-
Notifications
You must be signed in to change notification settings - Fork 495
/
Copy pathstable-release.js
334 lines (303 loc) · 10.1 KB
/
stable-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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
const chalk = require('chalk');
const {execSync} = require('child_process');
const minimist = require('minimist');
const path = require('path');
const {readJsonSync} = require('fs-extra');
const semver = require('semver');
const args = minimist(process.argv.slice(2));
const DEFAULT_SKIP_QA = false;
const skipQa = args['skip-qa'] || DEFAULT_SKIP_QA;
const branch = `master`;
const commitMsg = `chore(release): %v`;
const distTag = `latest`;
const remote = `origin`;
const cyan = (str) => chalk.cyan(str);
const execSyncInherit = (cmd) => execSync(cmd, {stdio: 'inherit'});
const log = (mark, str, which = 'log') => console[which](
mark, str.filter(s => !!s).join(` `)
);
const logError = (...str) => log(chalk.red(`✘`), str, 'error');
const logInfo = (...str) => log(chalk.blue(`ℹ`), str);
const logSuccess = (...str) => log(chalk.green(`✔`), str);
const logWarning = (...str) => log(chalk.yellow('‼︎'), str);
const failMsg = `${chalk.red(`STABLE RELEASE FAILED!`)} Stopping right here.`;
const runCommand = (cmd, inherit = true, display) => {
logInfo(`Running command ${cyan(display || cmd)}.`);
let out;
if (inherit) {
execSyncInherit(cmd);
} else {
out = execSync(cmd);
}
return out;
};
// eslint-disable-next-line complexity
(async () => {
if (!process.env.GH_TOKEN) {
logError(
`Environment variable ${cyan('GH_TOKEN')} was not defined or falsy. It`,
`must be defined with a properly scoped GitHub personal access token.`
);
logError(failMsg);
process.exit(1);
}
const lernaJsonPath = path.join(__dirname, '../lerna.json');
logInfo(`Reading ${cyan(lernaJsonPath)}...`);
let currentVersion, lernaJson, registry;
try {
lernaJson = readJsonSync(lernaJsonPath);
currentVersion = lernaJson.version;
if (!currentVersion) throw new Error('missing version in lerna.json');
registry = lernaJson.command.publish.registry;
if (!registry) throw new Error('missing registry in lerna.json');
} catch (e) {
console.error(e.stack || e);
logError(
`Could not read values from ${cyan(lernaJsonPath)}. Please check the`,
`error above.`
);
logError(failMsg);
process.exit(1);
}
try {
if (!semver(currentVersion).prerelease.length) {
logError(
`Current version in ${cyan('lerna.json')} is not a prerelease. This`,
`script is intended only for graduating a prerelease to a stable`,
`release.`
);
logError(failMsg);
process.exit(1);
}
logSuccess(
`Current version in ${cyan('lerna.json')} is ${cyan(currentVersion)}.`
);
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
logInfo(`Determining the latest stable version...`);
let latestStableVersion;
try {
const stableReleaseTags = runCommand(`git tag --list`, false)
.toString()
.trim()
.split('\n')
.filter(tag => tag.startsWith('v') && !tag.includes('-'))
.map(tag => tag.slice(1));
latestStableVersion = semver.rsort(stableReleaseTags)[0];
if (!latestStableVersion) {
logError(`Unable to determine the latest stable version.`);
logError(failMsg);
process.exit(1);
}
logSuccess(`Latest stable version is ${cyan(latestStableVersion)}.`);
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
let forcePublish;
try {
const currentMajor = semver(currentVersion).major;
const stableMajor = semver(latestStableVersion).major;
if (currentMajor > stableMajor) {
forcePublish = true;
} else if (currentMajor < stableMajor) {
logError(
`Major version of the current version in ${cyan('lerna.json')} is less`,
`than the major version of the latest stable version. wat.`
);
logError(failMsg);
process.exit(1);
}
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
logInfo(`Determining the current branch...`);
let currentBranch;
try {
currentBranch = runCommand(`git rev-parse --abbrev-ref HEAD`, false)
.toString()
.trim();
} catch (e) {
logError(`Could not determine the branch. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (currentBranch === branch) {
logSuccess(`Current branch and release branch are the same.`);
} else {
logError(
`Current branch ${cyan(currentBranch)} is not the same as release branch`,
`${cyan(branch)}.`
);
logError(failMsg);
process.exit(1);
}
logInfo(
`Fetching commits from ${cyan(remote)} to compare local and remote`,
`branches...`
);
try {
runCommand(`git fetch ${remote}`, false);
} catch (e) {
logError(`Could not fetch latest commits. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
let localRef, remoteRef;
try {
localRef = runCommand(`git rev-parse ${branch}`, false).toString().trim();
remoteRef = runCommand(`git rev-parse ${remote}/${branch}`, false)
.toString()
.trim();
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
if (localRef === remoteRef) {
logSuccess(`Local branch is in sync with remote branch.`);
} else {
logError(
`Local branch ${cyan(branch)} is not in sync with`,
`${cyan(`${remote}/${branch}`)}.`
);
logError(failMsg);
process.exit(1);
}
if (skipQa) {
logWarning(
`Skipping the QA suite. You already built the packages, right?`
);
} else {
logInfo(
`It's time to run the QA suite, this will take awhile...`
);
try {
runCommand(`npm run qa:full`);
logSuccess(`All steps succeeded in the QA suite.`);
} catch (e) {
logError(`A step failed in the QA suite. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
}
// The `--all` option is not supplied below because `lerna changed` is being
// used to calculate which packages should be tagged via:
// `npm dist-tag add [pkg]@[newStableVersion] nightly`
// Private packages are never tagged because dist-tags only pertain to
// packages published to the NPM registry. By tagging every newly released
// stable version of public packages as `nightly` (in addition to tagging as
// `latest`) we avoid version drift in nightly releases
const lernaChanged = [
`lerna`,
`changed`,
(!forcePublish && `--conventional-graduate`) || ``,
(forcePublish && `--force-publish`) || ``,
`--json`
].filter(str => !!str).join(` `);
let pkgsToTag;
try {
pkgsToTag = JSON.parse(
runCommand(`${lernaChanged} 2>/dev/null || true`, false, lernaChanged)
.toString()
.trim() || '[]'
);
} catch (e) {
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
}
const lernaPublish = [
`lerna`,
`publish`,
`--conventional-commits`,
(!forcePublish && `--conventional-graduate`) || ``,
`--create-release github`,
`--dist-tag ${distTag}`,
(forcePublish && `--force-publish`) || ``,
`--git-remote ${remote}`,
`--message "${commitMsg}"`,
`--registry ${registry}`
].filter(str => !!str).join(` `);
try {
runCommand(lernaPublish);
if (localRef ===
runCommand(`git rev-parse ${branch}`, false).toString().trim()) {
logWarning(
chalk.yellow(`STABLE RELEASE STOPPED!`),
`No commit or tag was created. No packages were published. This is`,
`most likely due to no qualifying changes having been made to the`,
`${cyan(branch)} branch since the previous release. Please check the`,
`output above.`
);
process.exit(0);
}
} catch (e) {
logError(`A problem occured. Please check the error above.`);
const lernaDocsUrl = 'https://github.com/lerna/lerna/tree/master/commands/publish#bump-from-package';
logError(
failMsg,
`Make sure to clean up commits, tags, and releases as necessary. Check`,
`the package registry to verify if any packages were published. If so`,
`they may need to be flagged as deprecated since the`,
`${cyan('lerna publish')} command exited with error. In some cases it`,
`may be possible to salvage an imcomplete release by using the`,
`${cyan('from-package')} keyword with the ${cyan('lerna publish')}`,
`command. See: ${lernaDocsUrl}`
);
process.exit(1);
}
if (pkgsToTag.length) {
logInfo(`Reading ${cyan(lernaJsonPath)}...`);
let updatedCurrentVersion;
try {
lernaJson = readJsonSync(lernaJsonPath);
updatedCurrentVersion = lernaJson.version;
if (!updatedCurrentVersion) throw new Error('missing version in lerna.json');
logSuccess(`Updated current version is ${updatedCurrentVersion}`);
} catch (e) {
console.error(e.stack || e);
logError(
`Could not read values from ${cyan(lernaJsonPath)}. Please check the`,
`error above.`
);
logError(failMsg);
process.exit(1);
}
logInfo(
`Updating ${cyan('nightly')} dist-tags to point to the new stable`,
`version...`
);
logInfo('Packages to tag:', pkgsToTag.map(({name}) => cyan(name)).join(', '));
const _pkgsToTag = pkgsToTag.slice();
try {
for (const {name} of pkgsToTag) {
runCommand(`npm dist-tag add ${name}@${updatedCurrentVersion} nightly`);
_pkgsToTag.shift();
}
} catch (e) {
logError(`A problem occured. Please check the error above.`);
const packages = _pkgsToTag.map(({name}) => cyan(name)).join(', ');
logError(
`NPM dist-tag ${cyan('nightly')} was not updated for the following`,
`packages: ${packages}. Make sure to complete the updates manually.`
);
logError(failMsg);
process.exit(1);
}
}
logSuccess(`${chalk.green(`STABLE RELEASE SUCCEEDED!`)} Woohoo! Done.`);
})().then(() => {
process.exit(0);
}).catch(e => {
console.error(e.stack || e);
logError(`A problem occured. Please check the error above.`);
logError(failMsg);
process.exit(1);
});