@samitouri / QOS-React / commits / 58b3ee7a88

Support untagged releases (#19507)

* Support untagged releases * Fix

Dan Abramov committed Jul 31, 2020 at 19:18 UTC 58b3ee7a889b62fefb9cc64962819795bebce7b8
10 files changed +117 -84
scripts/release/README.md
+15 -7
@@ -20,9 +20,13 @@ The high level process of creating releases is [documented below](#process). Ind
20
21 If this is your first time running the release scripts, go to the `scripts/release` directory and run `yarn` to install the dependencies.
22
23 -## Publishing Without Tags
23 +## Publishing Untagged
24
25 -The sections bekow include meaningful `--tags` in the instructions. However, keep in mind that **the `--tags` arguments is optional**, and you can omit it if you don't want to tag the release on npm at all. This can be useful when preparing breaking changes.
25 +The sections bekow include meaningful `--tag` in the instructions.
26 +
27 +However, keep in mind that **the `--tag` arguments is optional**, and you can omit it if you don't want to tag the release on npm at all. This can be useful when preparing breaking changes.
28 +
29 +Because npm requires a tag on publish, the script does it by creating a temporary tag and deleting it afterwards.
30
31 ## Publishing Next
32
@@ -42,7 +46,7 @@ scripts/release/prepare-release-from-ci.js --build=124756
46
47 Once the build has been checked out and tested locally, you're ready to publish it:
48 ```sh
45 -scripts/release/publish.js --tags next
49 +scripts/release/publish.js --tag next
50 ```
51
52 If the OTP code expires while publishing, re-run this command and answer "y" to the questions about whether it was expected for already published packages.
@@ -64,7 +68,7 @@ scripts/release/prepare-release-from-ci.js --build=124763
68 Once the build has been checked out and tested locally, you're ready to publish it. When publishing an experimental release, use the `experimental` tag:
69
70 ```sh
67 -scripts/release/publish.js --tags experimental
71 +scripts/release/publish.js --tag experimental
72 ```
73
74 If the OTP code expires while publishing, re-run this command and answer "y" to the questions about whether it was expected for already published packages.
@@ -86,11 +90,13 @@ This script will prompt you to select stable version numbers for each of the pac
90 Once this step is complete, you're ready to publish the release:
91
92 ```sh
89 -scripts/release/publish.js --tags latest
93 +scripts/release/publish.js --tag latest
94 ```
95
96 If the OTP code expires while publishing, re-run this command and answer "y" to the questions about whether it was expected for already published packages.
97
98 +Note that publishing the `latest` tag will always update the `next` tag automatically as well so they're in sync.
99 +
100 After successfully publishing the release, follow the on-screen instructions to ensure that all of the appropriate post-release steps are executed.
101
102 <sup>1: You can omit the `version` param if you just want to promote the latest "next" candidate to stable.</sup>
@@ -170,7 +176,9 @@ Upon completion, this script provides instructions for tagging the Git commit th
176 **Specify a `--dry` flag when running this script if you want to skip the NPM-publish step.** In this event, the script will print the NPM commands but it will not actually run them.
177
178 #### Example usage
173 -To publish a release to NPM as both `next` and `latest`:
179 +To publish a release to NPM as `latest`:
180 ```sh
175 -scripts/release/publish.js --tags latest
181 +scripts/release/publish.js --tag latest
182 ```
183 +
184 +Note that publishing the `latest` tag will always update the `next` tag automatically as well so they're in sync.
scripts/release/publish-commands/confirm-skipped-packages.js
+1 -1
@@ -6,7 +6,7 @@ const clear = require('clear');
6 const {confirm} = require('../utils');
7 const theme = require('../theme');
8
9 -const run = async ({cwd, packages, skipPackages, tags}) => {
9 +const run = async ({cwd, packages, skipPackages}) => {
10 if (skipPackages.length === 0) {
11 return;
12 }
scripts/release/publish-commands/confirm-version-and-tag.js renamed
+8 -16
@@ -8,24 +8,16 @@ const {join} = require('path');
8 const {confirm} = require('../utils');
9 const theme = require('../theme');
10
11 -const run = async ({cwd, packages, tags}) => {
11 +const run = async ({cwd, packages, tag}) => {
12 clear();
13
14 - if (tags.length === 0) {
15 - console.log(
16 - theme`{spinnerSuccess ✓} You are about the publish the following packages without any tags:`
17 - );
18 - } else if (tags.length === 1) {
19 - console.log(
20 - theme`{spinnerSuccess ✓} You are about the publish the following packages under the tag {tag ${tags}}:`
21 - );
22 - } else {
23 - console.log(
24 - theme`{spinnerSuccess ✓} You are about the publish the following packages under the tags {tag ${tags.join(
25 - ', '
26 - )}}:`
27 - );
28 - }
14 + // All latest releases are auto-tagged as next too by the script.
15 + let tags = tag === 'latest' ? ['latest', 'next'] : [tag];
16 + console.log(
17 + theme`{spinnerSuccess ✓} You are about the publish the following packages under the tag {tag ${tags.join(
18 + ', '
19 + )}}:`
20 + );
21
22 for (let i = 0; i < packages.length; i++) {
23 const packageName = packages[i];
scripts/release/publish-commands/parse-params.js
+13 -6
@@ -13,10 +13,10 @@ const paramDefinitions = [
13 defaultValue: false,
14 },
15 {
16 - name: 'tags',
16 + name: 'tag',
17 type: String,
18 - multiple: true,
19 - description: 'NPM tags to point to the new release.',
18 + description: 'NPM tag to point to the new release.',
19 + defaultValue: 'untagged',
20 },
21 {
22 name: 'skipPackages',
@@ -29,10 +29,17 @@ const paramDefinitions = [
29
30 module.exports = () => {
31 const params = commandLineArgs(paramDefinitions);
32 - if (!params.tags || !params.tags.length) {
33 - params.tags = [];
32 + switch (params.tag) {
33 + case 'latest':
34 + case 'next':
35 + case 'experimental':
36 + case 'untagged':
37 + break;
38 + default:
39 + console.error('Unknown tag: "' + params.tag + '"');
40 + process.exit(1);
41 + break;
42 }
43 splitCommaParams(params.skipPackages);
36 - splitCommaParams(params.tags);
44 return params;
45 };
scripts/release/publish-commands/print-follow-up-instructions.js
+3 -3
@@ -9,7 +9,7 @@ const {join} = require('path');
9 const theme = require('../theme');
10 const {execRead} = require('../utils');
11
12 -const run = async ({cwd, packages, tags}) => {
12 +const run = async ({cwd, packages, tag}) => {
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];
@@ -24,7 +24,7 @@ const run = async ({cwd, packages, tags}) => {
24
25 clear();
26
27 - if (tags.length === 1 && tags[0] === 'next') {
27 + if (tag === 'next') {
28 console.log(
29 theme`{header A "next" release} {version ${version}} {header has been published!}`
30 );
@@ -35,7 +35,7 @@ const run = async ({cwd, packages, tags}) => {
35 theme.caution`The release has been published but you're not done yet!`
36 );
37
38 - if (tags.includes('latest')) {
38 + if (tag === 'latest') {
39 console.log();
40 console.log(
41 theme.header`Please review and commit all local, staged changes.`
scripts/release/publish-commands/publish-to-npm.js
+16 -7
@@ -9,7 +9,7 @@ const {join} = require('path');
9 const {confirm, execRead} = require('../utils');
10 const theme = require('../theme');
11
12 -const run = async ({cwd, dry, packages, tags}, otp) => {
12 +const run = async ({cwd, dry, packages, tag}, otp) => {
13 clear();
14
15 for (let i = 0; i < packages.length; i++) {
@@ -34,25 +34,34 @@ const run = async ({cwd, dry, packages, tags}, otp) => {
34
35 // Publish the package and tag it.
36 if (!dry) {
37 - await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
37 + await exec(`npm publish --tag=${tag} --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}`));
42 + console.log(theme.command(` npm publish --tag=${tag} --otp=${otp}`));
43
44 - for (let j = 1; j < tags.length; j++) {
44 + if (tag === 'latest') {
45 + // Whenever we publish latest, also tag "next" automatically so they're in sync.
46 if (!dry) {
47 await exec(
47 - `npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`,
48 - {cwd: packagePath}
48 + `npm dist-tag add ${packageName}@${version} next --otp=${otp}`
49 );
50 }
51 console.log(
52 theme.command(
53 - ` npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`
53 + ` npm dist-tag add ${packageName}@${version} next --otp=${otp}`
54 )
55 );
56 + } else if (tag === 'untagged') {
57 + // npm doesn't let us publish without a tag at all,
58 + // so for one-off publishes we clean it up ourselves.
59 + if (!dry) {
60 + await exec(`npm dist-tag rm ${packageName}@untagged --otp=${otp}`);
61 + }
62 + console.log(
63 + theme.command(`npm dist-tag rm ${packageName}@untagged --otp=${otp}`)
64 + );
65 }
66 }
67 }
scripts/release/publish-commands/update-stable-version-numbers.js
+2 -2
@@ -6,8 +6,8 @@ const {readFileSync, writeFileSync} = require('fs');
6 const {readJson, writeJson} = require('fs-extra');
7 const {join} = require('path');
8
9 -const run = async ({cwd, packages, skipPackages, tags}) => {
10 - if (!tags.includes('latest')) {
9 +const run = async ({cwd, packages, skipPackages, tag}) => {
10 + if (tag !== 'latest') {
11 // Don't update version numbers for alphas.
12 return;
13 }
scripts/release/publish-commands/validate-tag.js new
+55
@@ -0,0 +1,55 @@
1 +#!/usr/bin/env node
2 +
3 +'use strict';
4 +
5 +const {readJson} = require('fs-extra');
6 +const {join} = require('path');
7 +const theme = require('../theme');
8 +
9 +const run = async ({cwd, packages, tag}) => {
10 + // Prevent a "next" release from ever being published as @latest
11 + // All canaries share a version number, so it's okay to check any of them.
12 + const arbitraryPackageName = packages[0];
13 + const packageJSONPath = join(
14 + cwd,
15 + 'build',
16 + 'node_modules',
17 + arbitraryPackageName,
18 + 'package.json'
19 + );
20 + const {version} = await readJson(packageJSONPath);
21 + const isExperimentalVersion = version.indexOf('experimental') !== -1;
22 + if (version.indexOf('0.0.0') === 0) {
23 + if (tag === 'latest') {
24 + if (isExperimentalVersion) {
25 + console.log(
26 + theme`{error Experimental release} {version ${version}} {error cannot be tagged as} {tag latest}`
27 + );
28 + } else {
29 + console.log(
30 + theme`{error Next release} {version ${version}} {error cannot be tagged as} {tag latest}`
31 + );
32 + }
33 + process.exit(1);
34 + } else if (tag === 'next' && isExperimentalVersion) {
35 + console.log(
36 + theme`{error Experimental release} {version ${version}} {error cannot be tagged as} {tag next}`
37 + );
38 + process.exit(1);
39 + } else if (tag === 'experimental' && !isExperimentalVersion) {
40 + console.log(
41 + theme`{error Next release} {version ${version}} {error cannot be tagged as} {tag experimental}`
42 + );
43 + process.exit(1);
44 + }
45 + } else {
46 + if (tag !== 'latest') {
47 + console.log(
48 + theme`{error Stable release} {version ${version}} {error cannot be tagged as} {tag ${tag}}`
49 + );
50 + process.exit(1);
51 + }
52 + }
53 +};
54 +
55 +module.exports = run;
scripts/release/publish-commands/validate-tags.js deleted
-38
@@ -1,38 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {readJson} = require('fs-extra');
6 -const {join} = require('path');
7 -const theme = require('../theme');
8 -
9 -const run = async ({cwd, packages, tags}) => {
10 - // Prevent a "next" release from ever being published as @latest
11 - // All canaries share a version number, so it's okay to check any of them.
12 - const arbitraryPackageName = packages[0];
13 - const packageJSONPath = join(
14 - cwd,
15 - 'build',
16 - 'node_modules',
17 - arbitraryPackageName,
18 - 'package.json'
19 - );
20 - const {version} = await readJson(packageJSONPath);
21 - if (version.indexOf('0.0.0') === 0) {
22 - if (tags.includes('latest')) {
23 - console.log(
24 - theme`{error Next release} {version ${version}} {error cannot be tagged as} {tag latest}`
25 - );
26 - process.exit(1);
27 - }
28 - } else {
29 - if (tags.includes('next')) {
30 - console.log(
31 - theme`{error Stable release} {version ${version}} {error cannot be tagged as} {tag next}`
32 - );
33 - process.exit(1);
34 - }
35 - }
36 -};
37 -
38 -module.exports = run;
scripts/release/publish.js
+4 -4
@@ -8,13 +8,13 @@ const theme = require('./theme');
8
9 const checkNPMPermissions = require('./publish-commands/check-npm-permissions');
10 const confirmSkippedPackages = require('./publish-commands/confirm-skipped-packages');
11 -const confirmVersionAndTags = require('./publish-commands/confirm-version-and-tags');
11 +const confirmVersionAndTag = require('./publish-commands/confirm-version-and-tag');
12 const parseParams = require('./publish-commands/parse-params');
13 const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions');
14 const promptForOTP = require('./publish-commands/prompt-for-otp');
15 const publishToNPM = require('./publish-commands/publish-to-npm');
16 const updateStableVersionNumbers = require('./publish-commands/update-stable-version-numbers');
17 -const validateTags = require('./publish-commands/validate-tags');
17 +const validateTag = require('./publish-commands/validate-tag');
18 const validateSkipPackages = require('./publish-commands/validate-skip-packages');
19
20 const run = async () => {
@@ -37,9 +37,9 @@ const run = async () => {
37 }
38 });
39
40 - await validateTags(params);
40 + await validateTag(params);
41 await confirmSkippedPackages(params);
42 - await confirmVersionAndTags(params);
42 + await confirmVersionAndTag(params);
43 await validateSkipPackages(params);
44 await checkNPMPermissions(params);
45 const otp = await promptForOTP(params);