[ci] Prepare publish workflow (#32488)
Fixes up a few things in the script and workflow to make it possible to run in CI without interactive prompts.
lauren committed
Feb 27, 2025 at 15:24 UTC
4c9392b43e9f39e17c18ef1c2cd0f0a14e85669c
9 files changed
+154
-72
.github/workflows/runtime_releases_from_npm_manual.yml
+44
-12
@@ -12,22 +12,22 @@ on:
12
description: Version to publish for the specified packages
13
type: string
14
only_packages:
15
- description: Space separated list of packages to publish on NPM. Use this OR skip_packages, not together.
15
+ description: Packages to publish (space separated)
16
type: string
17
skip_packages:
18
- description: Space separated list of packages to NOT publish on NPM. Use this OR only_packages, not together.
18
+ description: Packages to NOT publish (space separated)
19
type: string
20
tags:
21
- description: Space separated list of tags to tag the release with on NPM
21
+ description: NPM tags (space separated)
22
type: string
23
- default: "['untagged']"
23
+ default: untagged
24
dry:
25
required: true
26
- description: Don't actually publish, just run a dry run
26
+ description: Dry run instead of publish?
27
type: boolean
28
default: true
29
force_notify:
30
- description: Force a Discord notification
30
+ description: Force a Discord notification?
31
type: boolean
32
default: false
33
@@ -52,8 +52,8 @@ jobs:
52
embed-author-icon-url: ${{ github.event.sender.avatar_url }}
53
embed-title: '⚠️ Publishing release from NPM'
54
embed-description: |
55
- ```
56
- inputs: ${{ toJson(inputs) }}
55
+ ```json
56
+ ${{ toJson(inputs) }}
57
```
58
embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
59
@@ -80,12 +80,44 @@ jobs:
80
working-directory: scripts/release
81
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
82
- if: '${{ inputs.only_packages }}'
83
+ name: 'Prepare and publish ${{ inputs.only_packages }}'
84
run: |
84
- scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --onlyPackages=${{ inputs.only_packages }}
85
+ echo -e "===== Preparing release from NPM =====\n"
86
+ scripts/release/prepare-release-from-npm.js \
87
+ --ci \
88
+ --skipTests \
89
+ --version=${{ inputs.version_to_promote }} \
90
+ --publishVersion=${{ inputs.version_to_publish }} \
91
+ --onlyPackages=${{ inputs.only_packages }}
92
+
93
+ echo -e "\n\n===== Check prepared files =====\n"
94
ls -R build/node_modules
86
- # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --onlyPackages=${{ inputs.only_packages }} --dry=${{ inputs.dry || 'false' }}
95
+
96
+ echo -e "\n\n===== Publishing to NPM =====\n"
97
+ scripts/release/publish.js \
98
+ --ci \
99
+ --tags=${{ inputs.tags }} \
100
+ --publishVersion=${{ inputs.version_to_publish }} \
101
+ --onlyPackages=${{ inputs.only_packages }} \
102
+ --dry=${{ inputs.dry }}
103
- if: '${{ inputs.skip_packages }}'
104
+ name: 'Prepare and publish all packages EXCEPT ${{ inputs.skip_packages }}'
105
run: |
89
- scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --skipPackages=${{ inputs.skip_packages }}
106
+ echo -e "===== Preparing release from NPM =====\n"
107
+ scripts/release/prepare-release-from-npm.js \
108
+ --ci \
109
+ --skipTests \
110
+ --version=${{ inputs.version_to_promote }} \
111
+ --publishVersion=${{ inputs.version_to_publish }} \
112
+ --skipPackages=${{ inputs.skip_packages }}
113
+
114
+ echo -e "\n\n===== Check prepared files =====\n"
115
ls -R build/node_modules
91
- # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --skipPackages=${{ inputs.skip_packages }} --dry=${{ inputs.dry || 'false' }}
116
+
117
+ echo -e "\n\n===== Publishing to NPM =====\n"
118
+ scripts/release/publish.js \
119
+ --ci \
120
+ --tags=${{ inputs.tags }} \
121
+ --publishVersion=${{ inputs.version_to_publish }} \
122
+ --skipPackages=${{ inputs.skip_packages }} \
123
+ --dry=${{ inputs.dry }}
scripts/release/prepare-release-from-npm-commands/confirm-stable-version-numbers.js
+37
-35
@@ -7,7 +7,7 @@ const semver = require('semver');
7
const theme = require('../theme');
8
const {confirm} = require('../utils');
9
10
-const run = async ({skipPackages}, versionsMap) => {
10
+const run = async ({ci, skipPackages}, versionsMap) => {
11
const groupedVersionsMap = new Map();
12
13
// Group packages with the same source versions.
@@ -22,44 +22,46 @@ const run = async ({skipPackages}, versionsMap) => {
22
}
23
});
24
25
- // Prompt user to confirm or override each version group.
26
- const entries = [...groupedVersionsMap.entries()];
27
- for (let i = 0; i < entries.length; i++) {
28
- const [bestGuessVersion, packages] = entries[i];
29
- const packageNames = packages.map(name => theme.package(name)).join(', ');
25
+ if (ci !== true) {
26
+ // Prompt user to confirm or override each version group if not running in CI.
27
+ const entries = [...groupedVersionsMap.entries()];
28
+ for (let i = 0; i < entries.length; i++) {
29
+ const [bestGuessVersion, packages] = entries[i];
30
+ const packageNames = packages.map(name => theme.package(name)).join(', ');
31
31
- let version = bestGuessVersion;
32
- if (
33
- skipPackages.some(skipPackageName => packages.includes(skipPackageName))
34
- ) {
35
- await confirm(
36
- theme`{spinnerSuccess ✓} Version for ${packageNames} will remain {version ${bestGuessVersion}}`
37
- );
38
- } else {
39
- const defaultVersion = bestGuessVersion
40
- ? theme.version(` (default ${bestGuessVersion})`)
41
- : '';
42
- version =
43
- (await prompt(
44
- theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: `
45
- )) || bestGuessVersion;
46
- prompt.done();
47
- }
32
+ let version = bestGuessVersion;
33
+ if (
34
+ skipPackages.some(skipPackageName => packages.includes(skipPackageName))
35
+ ) {
36
+ await confirm(
37
+ theme`{spinnerSuccess ✓} Version for ${packageNames} will remain {version ${bestGuessVersion}}`
38
+ );
39
+ } else {
40
+ const defaultVersion = bestGuessVersion
41
+ ? theme.version(` (default ${bestGuessVersion})`)
42
+ : '';
43
+ version =
44
+ (await prompt(
45
+ theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: `
46
+ )) || bestGuessVersion;
47
+ prompt.done();
48
+ }
49
49
- // Verify a valid version has been supplied.
50
- try {
51
- semver(version);
50
+ // Verify a valid version has been supplied.
51
+ try {
52
+ semver(version);
53
53
- packages.forEach(packageName => {
54
- versionsMap.set(packageName, version);
55
- });
56
- } catch (error) {
57
- console.log(
58
- theme`{spinnerError ✘} Version {version ${version}} is invalid.`
59
- );
54
+ packages.forEach(packageName => {
55
+ versionsMap.set(packageName, version);
56
+ });
57
+ } catch (error) {
58
+ console.log(
59
+ theme`{spinnerError ✘} Version {version ${version}} is invalid.`
60
+ );
61
61
- // Prompt again
62
- i--;
62
+ // Prompt again
63
+ i--;
64
+ }
65
}
66
}
67
};
scripts/release/prepare-release-from-npm-commands/guess-stable-version-numbers.js
+35
-21
@@ -5,7 +5,10 @@
5
const semver = require('semver');
6
const {execRead, logPromise} = require('../utils');
7
8
-const run = async ({cwd, packages, skipPackages}, versionsMap) => {
8
+const run = async (
9
+ {cwd, packages, skipPackages, ci, publishVersion},
10
+ versionsMap
11
+) => {
12
const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
13
cwd,
14
});
@@ -13,30 +16,41 @@ const run = async ({cwd, packages, skipPackages}, versionsMap) => {
16
for (let i = 0; i < packages.length; i++) {
17
const packageName = packages[i];
18
16
- try {
17
- // In case local package JSONs are outdated,
18
- // guess the next version based on the latest NPM release.
19
- const version = await execRead(`npm show ${packageName} version`);
20
-
21
- if (skipPackages.includes(packageName)) {
22
- versionsMap.set(packageName, version);
19
+ if (ci === true) {
20
+ if (publishVersion != null) {
21
+ versionsMap.set(packageName, publishVersion);
22
} else {
24
- const {major, minor, patch} = semver(version);
25
-
26
- // Guess the next version by incrementing patch.
27
- // The script will confirm this later.
28
- // By default, new releases from mains should increment the minor version number,
29
- // and patch releases should be done from branches.
30
- if (branch === 'main') {
31
- versionsMap.set(packageName, `${major}.${minor + 1}.0`);
23
+ console.error(
24
+ 'When running in CI mode, a publishVersion must be supplied'
25
+ );
26
+ process.exit(1);
27
+ }
28
+ } else {
29
+ try {
30
+ // In case local package JSONs are outdated,
31
+ // guess the next version based on the latest NPM release.
32
+ const version = await execRead(`npm show ${packageName} version`);
33
+
34
+ if (skipPackages.includes(packageName)) {
35
+ versionsMap.set(packageName, version);
36
} else {
33
- versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
37
+ const {major, minor, patch} = semver(version);
38
+
39
+ // Guess the next version by incrementing patch.
40
+ // The script will confirm this later.
41
+ // By default, new releases from mains should increment the minor version number,
42
+ // and patch releases should be done from branches.
43
+ if (branch === 'main') {
44
+ versionsMap.set(packageName, `${major}.${minor + 1}.0`);
45
+ } else {
46
+ versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
47
+ }
48
}
49
+ } catch (error) {
50
+ // If the package has not yet been published,
51
+ // we'll require a version number to be entered later.
52
+ versionsMap.set(packageName, null);
53
}
36
- } catch (error) {
37
- // If the package has not yet been published,
38
- // we'll require a version number to be entered later.
39
- versionsMap.set(packageName, null);
54
}
55
}
56
};
scripts/release/prepare-release-from-npm-commands/parse-params.js
+11
@@ -39,6 +39,17 @@ const paramDefinitions = [
39
description:
40
'Version of published "next" release (e.g. 0.0.0-0e526bcec-20210202)',
41
},
42
+ {
43
+ name: 'publishVersion',
44
+ type: String,
45
+ description: 'Version to publish',
46
+ },
47
+ {
48
+ name: 'ci',
49
+ type: Boolean,
50
+ description: 'Run in automated environment, without interactive prompts.',
51
+ defaultValue: false,
52
+ },
53
];
54
55
module.exports = () => {
scripts/release/prepare-release-from-npm-commands/update-stable-version-numbers.js
+7
-3
@@ -9,7 +9,7 @@ const {join, relative} = require('path');
9
const {confirm, execRead, printDiff} = require('../utils');
10
const theme = require('../theme');
11
12
-const run = async ({cwd, packages, version}, versionsMap) => {
12
+const run = async ({cwd, packages, version, ci}, versionsMap) => {
13
const nodeModulesPath = join(cwd, 'build/node_modules');
14
15
// Cache all package JSONs for easy lookup below.
@@ -107,7 +107,9 @@ const run = async ({cwd, packages, version}, versionsMap) => {
107
printDependencies(packageJSON.dependencies, 'dependency');
108
printDependencies(packageJSON.peerDependencies, 'peer');
109
}
110
- await confirm('Do the versions above look correct?');
110
+ if (ci !== true) {
111
+ await confirm('Do the versions above look correct?');
112
+ }
113
114
clear();
115
@@ -167,7 +169,9 @@ const run = async ({cwd, packages, version}, versionsMap) => {
169
console.log(
170
theme`A full diff is available at {path ${relative(cwd, diffPath)}}.`
171
);
170
- await confirm('Do the changes above look correct?');
172
+ if (ci !== true) {
173
+ await confirm('Do the changes above look correct?');
174
+ }
175
} else {
176
console.log(
177
theme`Skipping React renderer version update because React is not included in the release.`
scripts/release/prepare-release-from-npm.js
+7
@@ -26,6 +26,13 @@ const run = async () => {
26
params.version = await getLatestNextVersion();
27
}
28
29
+ if (params.onlyPackages.length > 0 && params.skipPackages.length > 0) {
30
+ console.error(
31
+ '--onlyPackages and --skipPackages cannot be used together'
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
params.packages = await getPublicPackages(isExperimental);
37
params.packages = params.packages.filter(packageName => {
38
if (params.onlyPackages.length > 0) {
scripts/release/publish-commands/confirm-version-and-tags.js
+3
@@ -38,6 +38,9 @@ const run = async ({cwd, packages, tags, ci}) => {
38
console.log(
39
theme`• {package ${packageName}} {version ${packageJSON.version}}`
40
);
41
+ if (ci) {
42
+ console.log(packageJSON);
43
+ }
44
}
45
46
if (!ci) {
scripts/release/publish-commands/publish-to-npm.js
+3
-1
@@ -27,7 +27,9 @@ const run = async ({cwd, dry, tags, ci}, packageName, otp) => {
27
await confirm('Is this expected?');
28
}
29
} else {
30
- console.log(theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`);
30
+ console.log(
31
+ theme`{spinnerSuccess ✓} Publishing {package ${packageName}}${dry ? ' (dry-run)' : ''}`
32
+ );
33
34
// Publish the package and tag it.
35
if (!dry) {
scripts/release/publish.js
+7
@@ -31,6 +31,13 @@ const run = async () => {
31
params.cwd = join(__dirname, '..', '..');
32
params.packages = await getPublicPackages(isExperimental);
33
34
+ if (params.onlyPackages.length > 0 && params.skipPackages.length > 0) {
35
+ console.error(
36
+ '--onlyPackages and --skipPackages cannot be used together'
37
+ );
38
+ process.exit(1);
39
+ }
40
+
41
if (params.onlyPackages.length > 0) {
42
params.packages = params.packages.filter(packageName => {
43
return params.onlyPackages.includes(packageName);