UNPKG

6.64 kBJavaScriptView Raw
1/*
2 * chg
3 * https://github.com/heff/chg
4 *
5 * Copyright (c) 2014 heff
6 * Licensed under the Apache license.
7 */
8
9'use strict';
10
11var chg = module.exports = {};
12
13var fs = require('fs');
14var shell = require('shelljs');
15var moment = require('moment');
16
17var changeLogFile = 'CHANGELOG.md';
18var unreleasedTitle = '## HEAD (Unreleased)\n';
19var noItems = '_(none)_';
20var divider = '--------------------\n\n';
21
22var ERR_NO_CHANGELOG = changeLogFile+' does not exist. Change to the directory where it does exist, or run `init` to create one in the current directory.';
23
24function noopCallback(){}
25
26function getChangeLog(){
27 if (!fs.existsSync(changeLogFile)) {
28 return null;
29 }
30 return fs.readFileSync(changeLogFile, 'utf8');
31}
32
33/**
34* Callbacks are standard "node style"
35* @callback chgCallback
36* @param {object} Error
37* @param {string} responseMessage
38*/
39
40/**
41* Creates CHANGELOG.md if it does not exist.
42* @param {object} options
43* @param {chgCallback} callback - successfull returns the new changelog file.
44* @returns {string} changelog filename
45*/
46chg.init = function(options, callback){
47 options = options || {};
48 callback = callback || noopCallback;
49
50 if (fs.existsSync(changeLogFile)){
51 var err = new Error(changeLogFile + ' already exists');
52 callback(err);
53 return err;
54 }
55
56 var contents = 'CHANGELOG\n=========\n\n';
57 contents += unreleasedTitle + noItems + '\n\n' + divider;
58
59 fs.writeFileSync(changeLogFile, contents, 'utf8');
60
61
62 callback(null, changeLogFile);
63
64 return changeLogFile;
65};
66
67/**
68* Deletes CHANGELOG.md
69* @param {object} options
70* @param {chgCallback} callback - success returns the deleted changelog file
71* @returns {string} changelog filename
72*/
73chg.delete = function(options, callback){
74 options = options || {};
75 callback = callback || noopCallback;
76
77 if (!fs.existsSync(changeLogFile)) {
78 var err = new Error(changeLogFile+' does not exist');
79 callback(err);
80 return err;
81 }
82
83 shell.rm(changeLogFile);
84
85 callback(null, changeLogFile);
86
87 return changeLogFile;
88};
89
90/**
91* Add a new line to CHANGELOG.md
92* @param {string} line - the new line to be added
93* @param {object} options
94* @param {chgCallback} callback - success returns the newly added line
95* @returns {string} the new line added
96*/
97chg.add = function(line, options, callback){
98 var contents, sections, top;
99
100 options = options || {};
101 callback = callback || noopCallback;
102
103 // get existing contents
104 contents = getChangeLog();
105 if (!contents) {
106 var err = new Error(ERR_NO_CHANGELOG);
107 callback(err);
108 return err;
109 }
110
111 // if 'noItems' is there, remove it
112 contents = contents.replace(unreleasedTitle + noItems + '\n', unreleasedTitle);
113
114 // split on the divider including preceding newline
115 sections = contents.split('\n'+divider);
116 // add new line to the unreleased section
117 top = sections[0] + '* ' + line + '\n\n';
118
119 // combine new contents and write file
120 contents = top + divider + sections[1];
121 fs.writeFileSync(changeLogFile, contents, 'utf8');
122
123
124 callback(null, line);
125
126 return line;
127};
128
129/**
130* Creates a new release by bumping the version, moving everything in the unreleased head to a new section headed by the new version (and creating a new, empty head)
131* @param {string} version - the new release (this will be the title for the changelog)
132* @param {object} options
133* @param {string} options.date - date of the new release (defaults to the current date)
134* @param {string} options.version - release type to create (if version param is null)
135* @param {chgCallback} callback - success returns an object containing the new changes
136* @returns {object} changeData
137* @returns {object} changeData.title - new title to be created
138* @returns {object} changeData.changes - list of changes added under the new title
139* @returns {object} changeData.changelog - entire changelog contents
140*/
141chg.release = function(version, options, callback){
142 var date, contents, changes, title, err;
143
144 callback = callback || noopCallback;
145 options = options || {};
146 date = options.date || moment().format('YYYY-MM-DD');
147 version = version || options.version || null;
148
149 if (!version) {
150 err = new Error('Version required');
151 callback(err);
152 return err;
153 }
154
155 // get existing contents
156 contents = getChangeLog();
157 if (!contents) {
158 err = new Error(ERR_NO_CHANGELOG);
159 callback(err);
160 return err;
161 }
162
163 // get everything after the unreleased title
164 changes = contents.split(unreleasedTitle)[1];
165 // get only the unreleased changes
166 changes = changes.split('\n\n')[0];
167 // replace unreleased changes with noItems
168 contents = contents.replace(changes, noItems);
169
170 // build new release title
171 title = '##' + ' ' + version + ' ('+ date +')\n';
172 // add title at the top of the release section
173 contents = contents.replace(divider, divider + title + changes + '\n\n');
174
175 fs.writeFileSync(changeLogFile, contents, 'utf8');
176
177 var changeData = { title: title, changes: changes, changelog: contents };
178 callback(null, changeData);
179
180 return changeData;
181};
182
183/**
184 * Finds a section by title in the changelog, and if it exists, returns the information. If no title is found, an empty object is returned.
185 * @param {string} title - the title of the desired section
186 * @param {object} options
187 * @param {chgCallback} callback - success returns an object containing the requested section
188 * @returns {object} section
189 * @returns {string} section.title - title of the section found
190 * @returns {array} section.changes - changes in the section
191 * @returns {string} section.changesRaw - raw text of the changes in the section
192 */
193chg.find = function(title, options, callback) {
194 options = options || {};
195 callback = callback || noopCallback;
196
197 var err;
198 if (!title) {
199 err = new Error('Title required');
200 callback(err);
201 return err;
202 }
203
204 var contents = getChangeLog();
205
206 if (!contents) {
207 err = new Error(ERR_NO_CHANGELOG);
208 callback(err);
209 return err;
210 }
211
212 // Get everything from the title to the first empty line
213 var regex = new RegExp('(## '+ title +'.*)\n((.+\n)+)');
214 var result = regex.exec(contents);
215
216 if (!result) {
217 callback(null, {});
218 return {};
219 }
220
221 // Turn the changes into an array before handing them back to the callback
222 var changeArray = result[2].split('\n');
223
224 var resObj = {
225 title: result[1],
226 changesRaw: result[2],
227 changes: changeArray
228 };
229
230 callback(null, resObj);
231 return resObj;
232};