Skip to content

Commit 58b3ee7

Browse files
authored
Support untagged releases (facebook#19507)
* Support untagged releases * Fix
1 parent 7543459 commit 58b3ee7

10 files changed

+117
-84
lines changed

scripts/release/README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ The high level process of creating releases is [documented below](#process). Ind
2020

2121
If this is your first time running the release scripts, go to the `scripts/release` directory and run `yarn` to install the dependencies.
2222

23-
## Publishing Without Tags
23+
## Publishing Untagged
2424

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.
2630

2731
## Publishing Next
2832

@@ -42,7 +46,7 @@ scripts/release/prepare-release-from-ci.js --build=124756
4246

4347
Once the build has been checked out and tested locally, you're ready to publish it:
4448
```sh
45-
scripts/release/publish.js --tags next
49+
scripts/release/publish.js --tag next
4650
```
4751

4852
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
6468
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:
6569

6670
```sh
67-
scripts/release/publish.js --tags experimental
71+
scripts/release/publish.js --tag experimental
6872
```
6973

7074
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
8690
Once this step is complete, you're ready to publish the release:
8791

8892
```sh
89-
scripts/release/publish.js --tags latest
93+
scripts/release/publish.js --tag latest
9094
```
9195

9296
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.
9397

98+
Note that publishing the `latest` tag will always update the `next` tag automatically as well so they're in sync.
99+
94100
After successfully publishing the release, follow the on-screen instructions to ensure that all of the appropriate post-release steps are executed.
95101

96102
<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
170176
**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.
171177

172178
#### Example usage
173-
To publish a release to NPM as both `next` and `latest`:
179+
To publish a release to NPM as `latest`:
174180
```sh
175-
scripts/release/publish.js --tags latest
181+
scripts/release/publish.js --tag latest
176182
```
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const clear = require('clear');
66
const {confirm} = require('../utils');
77
const theme = require('../theme');
88

9-
const run = async ({cwd, packages, skipPackages, tags}) => {
9+
const run = async ({cwd, packages, skipPackages}) => {
1010
if (skipPackages.length === 0) {
1111
return;
1212
}

scripts/release/publish-commands/confirm-version-and-tags.js renamed to scripts/release/publish-commands/confirm-version-and-tag.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,16 @@ const {join} = require('path');
88
const {confirm} = require('../utils');
99
const theme = require('../theme');
1010

11-
const run = async ({cwd, packages, tags}) => {
11+
const run = async ({cwd, packages, tag}) => {
1212
clear();
1313

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+
);
2921

3022
for (let i = 0; i < packages.length; i++) {
3123
const packageName = packages[i];

scripts/release/publish-commands/parse-params.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const paramDefinitions = [
1313
defaultValue: false,
1414
},
1515
{
16-
name: 'tags',
16+
name: 'tag',
1717
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',
2020
},
2121
{
2222
name: 'skipPackages',
@@ -29,10 +29,17 @@ const paramDefinitions = [
2929

3030
module.exports = () => {
3131
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;
3442
}
3543
splitCommaParams(params.skipPackages);
36-
splitCommaParams(params.tags);
3744
return params;
3845
};

scripts/release/publish-commands/print-follow-up-instructions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {join} = require('path');
99
const theme = require('../theme');
1010
const {execRead} = require('../utils');
1111

12-
const run = async ({cwd, packages, tags}) => {
12+
const run = async ({cwd, packages, tag}) => {
1313
// All packages are built from a single source revision,
1414
// so it is safe to read build info from any one of them.
1515
const arbitraryPackageName = packages[0];
@@ -24,7 +24,7 @@ const run = async ({cwd, packages, tags}) => {
2424

2525
clear();
2626

27-
if (tags.length === 1 && tags[0] === 'next') {
27+
if (tag === 'next') {
2828
console.log(
2929
theme`{header A "next" release} {version ${version}} {header has been published!}`
3030
);
@@ -35,7 +35,7 @@ const run = async ({cwd, packages, tags}) => {
3535
theme.caution`The release has been published but you're not done yet!`
3636
);
3737

38-
if (tags.includes('latest')) {
38+
if (tag === 'latest') {
3939
console.log();
4040
console.log(
4141
theme.header`Please review and commit all local, staged changes.`

scripts/release/publish-commands/publish-to-npm.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {join} = require('path');
99
const {confirm, execRead} = require('../utils');
1010
const theme = require('../theme');
1111

12-
const run = async ({cwd, dry, packages, tags}, otp) => {
12+
const run = async ({cwd, dry, packages, tag}, otp) => {
1313
clear();
1414

1515
for (let i = 0; i < packages.length; i++) {
@@ -34,25 +34,34 @@ const run = async ({cwd, dry, packages, tags}, otp) => {
3434

3535
// Publish the package and tag it.
3636
if (!dry) {
37-
await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
37+
await exec(`npm publish --tag=${tag} --otp=${otp}`, {
3838
cwd: packagePath,
3939
});
4040
}
4141
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}`));
4343

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.
4546
if (!dry) {
4647
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}`
4949
);
5050
}
5151
console.log(
5252
theme.command(
53-
` npm dist-tag add ${packageName}@${version} ${tags[j]} --otp=${otp}`
53+
` npm dist-tag add ${packageName}@${version} next --otp=${otp}`
5454
)
5555
);
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+
);
5665
}
5766
}
5867
}

scripts/release/publish-commands/update-stable-version-numbers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const {readFileSync, writeFileSync} = require('fs');
66
const {readJson, writeJson} = require('fs-extra');
77
const {join} = require('path');
88

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') {
1111
// Don't update version numbers for alphas.
1212
return;
1313
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 0 additions & 38 deletions
This file was deleted.

scripts/release/publish.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ const theme = require('./theme');
88

99
const checkNPMPermissions = require('./publish-commands/check-npm-permissions');
1010
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');
1212
const parseParams = require('./publish-commands/parse-params');
1313
const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions');
1414
const promptForOTP = require('./publish-commands/prompt-for-otp');
1515
const publishToNPM = require('./publish-commands/publish-to-npm');
1616
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');
1818
const validateSkipPackages = require('./publish-commands/validate-skip-packages');
1919

2020
const run = async () => {
@@ -37,9 +37,9 @@ const run = async () => {
3737
}
3838
});
3939

40-
await validateTags(params);
40+
await validateTag(params);
4141
await confirmSkippedPackages(params);
42-
await confirmVersionAndTags(params);
42+
await confirmVersionAndTag(params);
4343
await validateSkipPackages(params);
4444
await checkNPMPermissions(params);
4545
const otp = await promptForOTP(params);

0 commit comments

Comments
 (0)