Some release script fixes (#20718)
* Retry loop should not start over from beginning When the otp times out, we should not retry the packages that were already successfully published. We should pick up where we left off. * Don't crash if build-info.json doesn't exist The "print follow up instructions" step crashes if build-info.json is not found. The new build workflow doesn't include those yet (might not need to) and since the instructions that depend on it only affect semver (latest) releases, I've moved the code to that branch. Will follow up with a proper fix, either by adding back a build-info.json file or removing that dependency and reading the commit some other way.
Andrew Clark committed
Feb 2, 2021 at 16:48 UTC
7e36d8beba53102e11c523f317976b311666e449
3 files changed
+121
-111
scripts/release/publish-commands/print-follow-up-instructions.js
+70
-56
@@ -10,13 +10,6 @@ const theme = require('../theme');
10
const {execRead} = require('../utils');
11
12
const run = async ({cwd, packages, tags}) => {
13
- // All packages are built from a single source revision,
14
- // so it is safe to read build info from any one of them.
15
- const arbitraryPackageName = packages[0];
16
- const {commit} = readJsonSync(
17
- join(cwd, 'build', 'node_modules', arbitraryPackageName, 'build-info.json')
18
- );
19
-
13
// Tags are named after the react version.
14
const {version} = readJsonSync(
15
`${cwd}/build/node_modules/react/package.json`
@@ -28,6 +21,10 @@ const run = async ({cwd, packages, tags}) => {
21
console.log(
22
theme`{header A "next" release} {version ${version}} {header has been published!}`
23
);
24
+ } else if (tags.length === 1 && tags[0] === 'experimental') {
25
+ console.log(
26
+ theme`{header An "experimental" release} {version ${version}} {header has been published!}`
27
+ );
28
} else {
29
const nodeModulesPath = join(cwd, 'build/node_modules');
30
@@ -36,6 +33,23 @@ const run = async ({cwd, packages, tags}) => {
33
);
34
35
if (tags.includes('latest')) {
36
+ // All packages are built from a single source revision,
37
+ // so it is safe to read build info from any one of them.
38
+ const arbitraryPackageName = packages[0];
39
+ // FIXME: New build script does not output build-info.json. It's only used
40
+ // by this post-publish print job, and only for "latest" releases, so I've
41
+ // disabled it as a workaround so the publish script doesn't crash for
42
+ // "next" and "experimental" pre-releases.
43
+ const {commit} = readJsonSync(
44
+ join(
45
+ cwd,
46
+ 'build',
47
+ 'node_modules',
48
+ arbitraryPackageName,
49
+ 'build-info.json'
50
+ )
51
+ );
52
+
53
console.log();
54
console.log(
55
theme.header`Please review and commit all local, staged changes.`
@@ -54,61 +68,61 @@ const run = async ({cwd, packages, tags}) => {
68
if (status) {
69
console.log(theme.path`• packages/shared/ReactVersion.js`);
70
}
57
- }
71
59
- console.log();
60
- console.log(
61
- theme`{header Don't forget to also update and commit the }{path CHANGELOG}`
62
- );
72
+ console.log();
73
+ console.log(
74
+ theme`{header Don't forget to also update and commit the }{path CHANGELOG}`
75
+ );
76
64
- // Prompt the release engineer to tag the commit and update the CHANGELOG.
65
- // (The script could automatically do this, but this seems safer.)
66
- console.log();
67
- console.log(
68
- theme.header`Tag the source for this release in Git with the following command:`
69
- );
70
- console.log(
71
- theme` {command git tag -a v}{version %s} {command -m "v%s"} {version %s}`,
72
- version,
73
- version,
74
- commit
75
- );
76
- console.log(theme.command` git push origin --tags`);
77
+ // Prompt the release engineer to tag the commit and update the CHANGELOG.
78
+ // (The script could automatically do this, but this seems safer.)
79
+ console.log();
80
+ console.log(
81
+ theme.header`Tag the source for this release in Git with the following command:`
82
+ );
83
+ console.log(
84
+ theme` {command git tag -a v}{version %s} {command -m "v%s"} {version %s}`,
85
+ version,
86
+ version,
87
+ commit
88
+ );
89
+ console.log(theme.command` git push origin --tags`);
90
78
- console.log();
79
- console.log(theme.header`Lastly, please fill in the release on GitHub.`);
80
- console.log(
81
- theme.link`https://github.com/facebook/react/releases/tag/v%s`,
82
- version
83
- );
84
- console.log(
85
- theme`\nThe GitHub release should also include links to the following artifacts:`
86
- );
87
- for (let i = 0; i < packages.length; i++) {
88
- const packageName = packages[i];
89
- if (existsSync(join(nodeModulesPath, packageName, 'umd'))) {
90
- const {version: packageVersion} = readJsonSync(
91
- join(nodeModulesPath, packageName, 'package.json')
92
- );
93
- console.log(
94
- theme`{path • %s:} {link https://unpkg.com/%s@%s/umd/}`,
95
- packageName,
96
- packageName,
97
- packageVersion
98
- );
91
+ console.log();
92
+ console.log(theme.header`Lastly, please fill in the release on GitHub.`);
93
+ console.log(
94
+ theme.link`https://github.com/facebook/react/releases/tag/v%s`,
95
+ version
96
+ );
97
+ console.log(
98
+ theme`\nThe GitHub release should also include links to the following artifacts:`
99
+ );
100
+ for (let i = 0; i < packages.length; i++) {
101
+ const packageName = packages[i];
102
+ if (existsSync(join(nodeModulesPath, packageName, 'umd'))) {
103
+ const {version: packageVersion} = readJsonSync(
104
+ join(nodeModulesPath, packageName, 'package.json')
105
+ );
106
+ console.log(
107
+ theme`{path • %s:} {link https://unpkg.com/%s@%s/umd/}`,
108
+ packageName,
109
+ packageName,
110
+ packageVersion
111
+ );
112
+ }
113
}
100
- }
114
102
- // Update reactjs.org so the React version shown in the header is up to date.
103
- console.log();
104
- console.log(
105
- theme.header`Once you've pushed changes, update the docs site.`
106
- );
107
- console.log(
108
- 'This will ensure that any newly-added error codes can be decoded.'
109
- );
115
+ // Update reactjs.org so the React version shown in the header is up to date.
116
+ console.log();
117
+ console.log(
118
+ theme.header`Once you've pushed changes, update the docs site.`
119
+ );
120
+ console.log(
121
+ 'This will ensure that any newly-added error codes can be decoded.'
122
+ );
123
111
- console.log();
124
+ console.log();
125
+ }
126
}
127
};
128
scripts/release/publish-commands/publish-to-npm.js
+41
-51
@@ -3,70 +3,60 @@
3
'use strict';
4
5
const {exec} = require('child-process-promise');
6
-const clear = require('clear');
6
const {readJsonSync} = require('fs-extra');
7
const {join} = require('path');
8
const {confirm, execRead} = require('../utils');
9
const theme = require('../theme');
10
12
-const run = async ({cwd, dry, packages, tags}, otp) => {
13
- clear();
11
+const run = async ({cwd, dry, tags}, packageName, otp) => {
12
+ const packagePath = join(cwd, 'build/node_modules', packageName);
13
+ const {version} = readJsonSync(join(packagePath, 'package.json'));
14
15
- for (let i = 0; i < packages.length; i++) {
16
- const packageName = packages[i];
17
- const packagePath = join(cwd, 'build/node_modules', packageName);
18
- const {version} = readJsonSync(join(packagePath, 'package.json'));
15
+ // Check if this package version has already been published.
16
+ // If so we might be resuming from a previous run.
17
+ // We could infer this by comparing the build-info.json,
18
+ // But for now the easiest way is just to ask if this is expected.
19
+ const info = await execRead(`npm view ${packageName}@${version}`);
20
+ if (info) {
21
+ console.log(
22
+ theme`{package ${packageName}} {version ${version}} has already been published.`
23
+ );
24
+ await confirm('Is this expected?');
25
+ } else {
26
+ console.log(theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`);
27
20
- // Check if this package version has already been published.
21
- // If so we might be resuming from a previous run.
22
- // We could infer this by comparing the build-info.json,
23
- // But for now the easiest way is just to ask if this is expected.
24
- const info = await execRead(`npm view ${packageName}@${version}`);
25
- if (info) {
26
- console.log(
27
- theme`{package ${packageName}} {version ${version}} has already been published.`
28
- );
29
- await confirm('Is this expected?');
30
- } else {
31
- console.log(
32
- theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`
33
- );
28
+ // Publish the package and tag it.
29
+ if (!dry) {
30
+ await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
31
+ cwd: packagePath,
32
+ });
33
+ }
34
+ console.log(theme.command(` cd ${packagePath}`));
35
+ console.log(theme.command(` npm publish --tag=${tags[0]} --otp=${otp}`));
36
35
- // Publish the package and tag it.
37
+ for (let j = 1; j < tags.length; j++) {
38
if (!dry) {
37
- await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
38
- cwd: packagePath,
39
- });
40
- }
41
- console.log(theme.command(` cd ${packagePath}`));
42
- console.log(theme.command(` npm publish --tag=${tags[0]} --otp=${otp}`));
43
-
44
- for (let j = 1; j < tags.length; j++) {
45
- if (!dry) {
46
- await exec(
47
- `npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`,
48
- {cwd: packagePath}
49
- );
50
- }
51
- console.log(
52
- theme.command(
53
- ` npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`
54
- )
39
+ await exec(
40
+ `npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`,
41
+ {cwd: packagePath}
42
);
43
}
44
+ console.log(
45
+ theme.command(
46
+ ` npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`
47
+ )
48
+ );
49
+ }
50
58
- if (tags.includes('untagged')) {
59
- // npm doesn't let us publish without a tag at all,
60
- // so for one-off publishes we clean it up ourselves.
61
- if (!dry) {
62
- await exec(`npm dist-tag rm ${packageName} untagged --otp=${otp}`);
63
- }
64
- console.log(
65
- theme.command(
66
- ` npm dist-tag rm ${packageName} untagged --otp=${otp}`
67
- )
68
- );
51
+ if (tags.includes('untagged')) {
52
+ // npm doesn't let us publish without a tag at all,
53
+ // so for one-off publishes we clean it up ourselves.
54
+ if (!dry) {
55
+ await exec(`npm dist-tag rm ${packageName} untagged --otp=${otp}`);
56
}
57
+ console.log(
58
+ theme.command(` npm dist-tag rm ${packageName} untagged --otp=${otp}`)
59
+ );
60
}
61
}
62
};
scripts/release/publish.js
+10
-4
@@ -4,6 +4,7 @@
4
5
const {join} = require('path');
6
const {readJsonSync} = require('fs-extra');
7
+const clear = require('clear');
8
const {getPublicPackages, handleError} = require('./utils');
9
const theme = require('./theme');
10
@@ -49,17 +50,22 @@ const run = async () => {
50
await validateSkipPackages(params);
51
await checkNPMPermissions(params);
52
52
- while (true) {
53
+ clear();
54
+ let otp = await promptForOTP(params);
55
+ const packageNames = params.packages;
56
+ for (let i = 0; i < packageNames.length; ) {
57
+ const packageName = packageNames[i];
58
try {
54
- const otp = await promptForOTP(params);
55
- await publishToNPM(params, otp);
56
- break;
59
+ await publishToNPM(params, packageName, otp);
60
+ i++;
61
} catch (error) {
62
console.error(error.message);
63
console.log();
64
console.log(
65
theme.error`Publish failed. Enter a fresh otp code to retry.`
66
);
67
+ otp = await promptForOTP(params);
68
+ // Try publishing package again
69
continue;
70
}
71
}