This repository has been archived by the owner on Oct 20, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathrelease.js
256 lines (211 loc) · 8.93 KB
/
release.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/**
* This file can be used to generate automatically new releases of the source code.
* It can either generate major/minor releases based on development branch or hotfix releases based on master branch.
*
* Usage and process to generate a new major/minor release:
* 1 - Ensure your working copy is clear and that eveything has been pushed/pulled into/from origin/remote repository
* 2 - Start a new git flow by using this script:
* # node release.js --start [--type major|minor] [--version <version>]
* If version is not provided, the version number will be automatically updated according to release type.
* This script will:
* a - start a new release git flow (create a release branch from development branch)
* b - update the package.json file with the new release version number
* c - modify the RELEASES_NOTES.txt file by inserting a new section for the release and appending all commits messages
* 3 - Modify the RELEASES_NOTES.txt file to format and finalize the release notes
* 4 - Commit and push the RELEASES_NOTES.txt file
* 5 - Once the release branch tested/validated and ready to be merged, finish the git flow by executing the script:
* # node release.js --finish
* This script will:
* a - Finish the release git flow (merge release branch into master branch and add a tag)
* b - Modify the package.json file on development branch to setup next version number (in development phase, with suffix '-dev')
*
* Usage and process to generate a new hotfix release:
* 1 - Ensure your working copy is clear and that eveything has been pushed/pulled into/from origin/remote repository
* 2 - Start a new git flow by using this script:
* # node release.js --start [--type patch] [--version <version>]
* If version is not provided, the version number will be automatically updated according to release type.
* This script will:
* a - start a new release git flow (create a hotfix branch from master branch)
* b - update the package.json file with the new release version number
* c - modify the RELEASES_NOTES.txt file by inserting a new section for the release and appending all commits messages
* 3 - Modify the RELEASES_NOTES.txt file to format and finalize the release notes
* 4 - Commit and push the RELEASES_NOTES.txt file
* 5 - Once the release branch tested/validated and ready to be merged, finish the git flow by executing the script:
* # node release.js --finish
* This script will finish the release git flow (merge hotfix branch into master and development branches and add a tag).
*/
var PACKAGE_JSON_FILE = './package.json',
RELEASES_NOTES_FILE = './RELEASES NOTES.txt';
var child = require('child_process'),
fs = require('fs'),
semver = require('semver'),
yargs = require('yargs'),
argv = yargs
.usage("$0 --start [--type major|minor|patch] [--ver <version>] \n$0 --finish")
.default('type', 'minor')
.argv;
function execSync(command) {
console.info('Exec command: ' + command);
var res = child.execSync(command);
res = String(res).trim();
return res;
}
function gitGetCurrentBranch() {
return execSync('git rev-parse --abbrev-ref HEAD');
}
function gitIsRepoClean() {
// '-uno' => do not hwo untracked files
return execSync('git status -s -uno').length === 0;
}
function gitGetLastTag() {
return execSync('git describe --abbrev=0 --tags');
}
function gitGetCommits(startTag, endTag) {
return execSync('git log ' + startTag + '...' + endTag + ' --format=%f').split('\n');
}
function gitCheckout(branch) {
return execSync('git checkout ' + branch);
}
function gitPull() {
return execSync('git pull --all');
}
function gitCommit(message) {
if (!message || message.length === 0) {
console.error('Please provide a commit message');
return;
}
return execSync('git commit -am \"' + message + '\"');
}
function gitPush() {
execSync('git push --all');
execSync('git push --tags');
}
function gitFlowStart(type, version) {
return execSync('git flow ' + type + ' start ' + version);
}
function gitFlowFinish(type, version) {
try {
execSync('git flow ' + type + ' finish -F ' + version + ' -m \"' + type + ' v' + version + '\"');
} catch (err) {
// In case of hotfix, there will be a conflict when merging hotfix branch into development with package.json file (version value)
// Then resolve the merge and finish again the hotfix
if (type === 'hotfix') {
execSync('git checkout --ours package.json');
execSync('git commit -am \"Merge tag v' + version + ' into development\"');
execSync('git flow ' + type + ' finish -F ' + version + ' -m \"' + type + ' v' + version + '\"');
}
}
}
function prependFile(path, data) {
var options = {
encoding: 'utf8',
mode: 438 /*=0666*/
},
appendOptions = {
encoding: options.encoding,
mode: options.mode,
flags: 'w'
},
currentFileData = "";
// Open and read input file
try {
currentFileData = fs.readFileSync(path, options);
} catch (err) {
console.error('Failed to open file ' + path);
return;
}
// Prepend data and write file
fs.writeFileSync(path, data + currentFileData, appendOptions);
}
function generateReleaseNotes(version) {
var notes= "";
// Get current date
var date = new Date(),
y = date.getFullYear().toString(),
m = (date.getMonth() + 1).toString(),
d = date.getDate().toString(),
MM = m[1] ? m : "0" + m[0],
DD = d[1] ? d : "0" + d[0];
notes = '### Release Notes v' + version + ' (' + y + '/' + MM + '/' + DD + ')\n';
// Get last/previous tag
var lastTag = gitGetLastTag();
// Get commits since last tag
var commits = gitGetCommits(lastTag, 'HEAD');
for (var i =0; i < commits.length; i++) {
notes += '* ' + commits[i] + '\n';
}
notes += '\n';
return notes;
}
function startRelease() {
var releaseType = argv.type === 'patch' ? 'hotfix' : 'release';
// Check if repository is clean
if (!gitIsRepoClean()) {
console.error("Repository is not clean");
return;
}
if (releaseType === 'hotfix') {
// Checkout master branch
gitCheckout('master');
} else {
// Checkout development branch
gitCheckout('development');
}
// Read package.json file
var pkg = require(PACKAGE_JSON_FILE);
// Get current version from package.json and increment it:
// - if version ends with '-dev' suffix, then suffix is removed
// - else version number is incremented
console.info("Current version: " + pkg.version);
console.info("Release type: " + argv.type);
var version = argv.ver ? argv.ver : semver.inc(pkg.version, argv.type);
pkg.version = version;
console.info("=> Release version: " + pkg.version);
// Start git flow release
console.info('Start git ' + releaseType + ' v' + pkg.version);
gitFlowStart(releaseType, pkg.version);
// Write/update and commit package.jon file with the new version number
fs.writeFileSync(PACKAGE_JSON_FILE, JSON.stringify(pkg, null, ' '), {encoding: 'utf8',mode: 438 /*=0666*/});
gitCommit('v' + pkg.version);
// Generate release notes, write/update and commit 'RELEASE NOTES.txt' file
var notes = generateReleaseNotes(version);
prependFile(RELEASES_NOTES_FILE, notes);
console.info("Please complete and commit release notes...");
}
function finishRelease() {
// Get flow type
var branch = gitGetCurrentBranch(),
releaseType = branch.startsWith('release/') ? 'release' : (branch.startsWith('hotfix/') ? 'hotfix' : null);
// Check if we are on release branch
if (releaseType === null) {
console.error('Current branch = ' + branch + '. Please checkout current release/hotfix branch');
return;
}
// Update local branches
// gitPull();
// Read package.json file
var pkg = require(PACKAGE_JSON_FILE);
// Finish git flow
console.info('Finish git ' + releaseType + ' v' + pkg.version);
gitFlowFinish(releaseType, pkg.version);
if (releaseType === 'release') {
// Increment version number for next release version in development
gitCheckout('development');
var version = semver.inc(pkg.version, 'minor');
version += '-dev';
pkg.version = version;
console.info("Next release version in development: " + pkg.version);
fs.writeFileSync(PACKAGE_JSON_FILE, JSON.stringify(pkg, null, ' '), {encoding: 'utf8',mode: 438 /*=0666*/});
gitCommit('v' + pkg.version);
}
// Push all branches and tags to remote
gitPush();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
if (argv.start) {
startRelease();
} else if (argv.finish) {
finishRelease();
} else {
yargs.showHelp();
}