Updated release script documentation and command names (#17929)

* Updated release script documentation and command names

* Update scripts/release/README.md

Co-Authored-By: Sunil Pai <[email protected]>

* Updated README

Co-authored-by: Sunil Pai <[email protected]>
This commit is contained in:
Brian Vaughn
2020-02-05 08:52:31 -08:00
committed by GitHub
co-authored by Sunil Pai
parent f4618d8f91
commit f2e6061ff6
21 changed files with 86 additions and 81 deletions
+44 -39
View File
@@ -4,12 +4,12 @@ The release process consists of several phases, each one represented by one of t
A typical release goes like this:
1. When a commit is pushed to the React repo, [Circle CI](https://circleci.com/gh/facebook/react/) will build all release bundles and run unit tests against both the source code and the built bundles.
2. Next the release is [**published as a canary**](#publishing-a-canary) using the [`prepare-canary`](#prepare-canary) and [`publish`](#publish) scripts. (Currently this process is manual but might be automated in the future using [GitHub "actions"](https://github.com/features/actions).)
3. Finally, a canary releases can be [**promoted to stable**](#publishing-a-stable-release)<sup>1</sup> using the [`prepare-stable`](#prepare-stable) and [`publish`](#publish) scripts. (This process is always manual.)
2. The release is then [**published to the `next` channel**](#publishing-next) using the [`prepare-next`](#prepare-next) and [`publish`](#publish) scripts. (Currently this process is manual but might be automated in the future using [GitHub "actions"](https://github.com/features/actions).)
3. Finally, a "next" release can be [**promoted to stable**](#publishing-a-stable-release)<sup>1</sup> using the [`prepare-stable`](#prepare-stable) and [`publish`](#publish) scripts. (This process is always manual.)
The high level process of creating releases is [documented below](#process). Individual scripts are documented as well:
* [`create-canary`](#create-canary)
* [`prepare-canary`](#prepare-canary)
* [`create-next`](#create-next)
* [`prepare-next`](#prepare-next)
* [`prepare-stable`](#prepare-stable)
* [`publish`](#publish)
@@ -19,36 +19,39 @@ The high level process of creating releases is [documented below](#process). Ind
If this is your first time running the release scripts, go to the `scripts/release` directory and run `yarn` to install the dependencies.
## Publishing a Canary
## Publishing Next
Canaries are meant to be lightweight and published often. In most cases, canaries can be published using artifacts built by Circle CI.
"Next" builds are meant to be lightweight and published often. In most cases, they can be published using artifacts built by Circle CI.
To prepare a canary for a particular commit:
To prepare a build for a particular commit:
1. Choose a commit from [the commit log](https://github.com/facebook/react/commits/master).
2. Click the "“✓" icon and click the Circle CI "Details" link.
3. Select the `build` job (**not** the `build_experimental` job; see the next section). If it's still pending, you'll need to wait for it to finish. (Note: This is the most awkward part of cutting a release right now. We have plans to improve it.)
4. Copy the build ID from the URL (e.g. the build ID for [circleci.com/gh/facebook/react/13471](https://circleci.com/gh/facebook/react/13471) is **13471**).
5. Run the [`prepare-canary`](#prepare-canary) script with the build ID you found <sup>1</sup>:
3. Select the `build` job (**not** the `build_experimental` job; see the next section).
* If it's still pending, you'll need to wait for it to finish. <sup>1</sup>
4. Copy the build ID from the URL
* e.g. the build ID for commit [241c4467](https://github.com/facebook/react/commit/241c4467eef7c2a8858c96d5dfe4e8ef84c47bad) is [**80592**](https://circleci.com/gh/facebook/react/80592)
5. Run the [`prepare-next`](#prepare-next) script with the build ID <sup>2</sup> you found:
```sh
scripts/release/prepare-canary.js --build=13471
scripts/release/prepare-next.js --build=80592
```
Once the canary has been checked out and tested locally, you're ready to publish it:
Once the build has been checked out and tested locally, you're ready to publish it:
```sh
scripts/release/publish.js --tags next
```
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.
<sup>1: You can omit the `build` param if you just want to release the latest commit as a canary.</sup>
<sup>1: This is the most awkward part of cutting a release right now. We have plans to improve it.</sup><br/>
<sup>2: You can omit the `build` param if you just want to release the latest commit as to "next".</sup>
## Publishing an Experimental Canary
## Publishing an Experimental Release
Experimental canaries are special releases with additional features turned on.
Experimental releases are special because they have additional features turned on.
The steps for publishing an experimental canary are almost the same as for publishing a normal canary, except in step 3 you should choose the `build_experimental` job instead of `build`. (I know this is awkward; we have plans to make it less so. Ideally these canaries would get published by a cron job.)
The steps for publishing an experimental release are almost the same as for publishing a "next" release, except in step 3 you should choose the `build_experimental` job instead of `build` <sup>1</sup> For example, the experimental build ID for commit [241c4467](https://github.com/facebook/react/commit/241c4467eef7c2a8858c96d5dfe4e8ef84c47bad) is [**80590**](https://circleci.com/gh/facebook/react/80590):
When publishing an experimental canary, use the `experimental` tag:
When publishing an experimental release, use the `experimental` tag:
```sh
scripts/release/publish.js --tags experimental
@@ -56,14 +59,16 @@ scripts/release/publish.js --tags experimental
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.
<sup>1: We have plans to make this less awkward. Ideally these releases will be published by a cron job.</sup>
## Publishing a Stable Release
Stable releases should always be created from a previously-released canary. This encourages better testing of the actual release artifacts and reduces the chance of unintended changes accidentally being included in a stable release.
Stable releases should always be created from the "next" channel. This encourages better testing of the actual release artifacts and reduces the chance of unintended changes accidentally being included in a stable release.
To prepare a stable release, choose a canary version and run the [`prepare-stable`](#prepare-stable) script <sup>1</sup>:
To prepare a stable release, choose a "next" version and run the [`prepare-stable`](#prepare-stable) script <sup>1</sup>:
```sh
scripts/release/prepare-stable.js --version=0.0.0-5bf84d292
scripts/release/prepare-stable.js --version=0.0.0-241c4467e
```
This script will prompt you to select stable version numbers for each of the packages. It will update the package JSON versions (and dependencies) based on the numbers you select.
@@ -71,14 +76,14 @@ This script will prompt you to select stable version numbers for each of the pac
Once this step is complete, you're ready to publish the release:
```sh
scripts/release/publish.js --tags next latest
scripts/release/publish.js --tags latest
```
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.
After successfully publishing the release, follow the on-screen instructions to ensure that all of the appropriate post-release steps are executed.
<sup>1: You can omit the `version` param if you just want to promote the latest canary to stable.</sup>
<sup>1: You can omit the `version` param if you just want to promote the latest "next" candidate to stable.</sup>
## Creating a Patch Release
@@ -96,53 +101,53 @@ Next cherry pick any changes from master that you want to include in the release
git cherry-pick <commit-hash>
```
Once you have cherry picked all of the commits you want to include in the release, push your feature branch and create a Pull Request (so that Circle CI will create a canary):
Once you have cherry picked all of the commits you want to include in the release, push your feature branch and create a Pull Request (so that Circle CI will create a build):
```sh
git push origin 16.8.3
```
Once CI is complete, follow the regular [**canary**](#publishing-a-canary) and [**promote to stable**](#publishing-a-stable-release) processes.
Once CI is complete, follow the regular [**next**](#publishing-next) and [**promote to stable**](#publishing-a-stable-release) processes.
<sup>1: The `build-info.json` artifact can also be used to identify the appropriate commit (e.g. [unpkg.com/[email protected]/build-info.json](https://unpkg.com/[email protected]/build-info.json) shows us that react version 16.8.3 was created from commit [`29b7b775f`](https://github.com/facebook/react/commit/29b7b775f)).</sup>
# Scripts
## `create-canary`
Creates a canary build from the current (local) Git revision.
## `create-next`
Creates a "next" build from the current (local) Git revision.
**This script is an escape hatch.** It allows a canary release to be created without pushing a commit to be verified by Circle CI. **It does not run any automated unit tests.** Testing is solely the responsibility of the release engineer.
**This script is an escape hatch.** It allows a release to be created without pushing a commit to be verified by Circle CI. **It does not run any automated unit tests.** Testing is solely the responsibility of the release engineer.
Note that this script git-archives the React repo (at the current revision) to a temporary directory before building, so **uncommitted changes are not included in the build**.
#### Example usage
To create a canary from the current branch and revision:
To create a build from the current branch and revision:
```sh
scripts/release/create-canary.js
scripts/release/create-next.js
```
## `prepare-canary`
Downloads build artifacts from Circle CI in preparation to be published to NPM as a canary release.
## `prepare-next`
Downloads build artifacts from Circle CI in preparation to be published to NPM as a "next" release.
All artifacts built by Circle CI have already been unit-tested (both source and bundles) but canaries should **always be manually tested** before being published. Upon completion, this script prints manual testing instructions.
All artifacts built by Circle CI have already been unit-tested (both source and bundles) but these candidates should **always be manually tested** before being published. Upon completion, this script prints manual testing instructions.
#### Example usage
To prepare the artifacts created by [Circle CI build 12677](https://circleci.com/gh/facebook/react/12677#artifacts/containers/0) you would run:
To prepare the artifacts created by [Circle CI build 80592](https://circleci.com/gh/facebook/react/80592#artifacts/containers/0) you would run:
```sh
scripts/release/prepare-canary.js --build=12677
scripts/release/prepare-next.js --build=80592
```
## `prepare-stable`
Checks out a canary release from NPM and prepares it to be published as a stable release.
Checks out a "next" release from NPM and prepares it to be published as a stable release.
This script prompts for new (stable) release versions for each public package and updates the package contents (both `package.json` and inline version numbers) to match. It also updates inter-package dependencies to account for the new versions.
Canary release have already been tested but it is still a good idea to **manually test and verify a release** before publishing to ensure that e.g. version numbers are correct. Upon completion, this script prints manual testing instructions.
"Next" releases have already been tested but it is still a good idea to **manually test and verify a release** before publishing to ensure that e.g. version numbers are correct. Upon completion, this script prints manual testing instructions.
#### Example usage
To promote the canary release `0.0.0-5bf84d292` (aka commit [5bf84d292](https://github.com/facebook/react/commit/5bf84d292)) to stable:
To promote the "next" release `0.0.0-241c4467e` (aka commit [241c4467e](https://github.com/facebook/react/commit/241c4467e)) to stable:
```sh
scripts/release/prepare-stable.js --version=0.0.0-5bf84d292
scripts/release/prepare-stable.js --version=0.0.0-241c4467e
```
## `publish`
@@ -157,5 +162,5 @@ Upon completion, this script provides instructions for tagging the Git commit th
#### Example usage
To publish a release to NPM as both `next` and `latest`:
```sh
scripts/release/publish.js --tags next latest
scripts/release/publish.js --tags latest
```
+1 -1
View File
@@ -7,7 +7,7 @@
// It exists to share code between the Node release scripts and CI bash scripts.
// IMPORTANT:
// Changes below should be mirrored in ./create-canary-commands/add-build-info-json.js
// Changes below should be mirrored in ./create-next-commands/add-build-info-json.js
const {exec} = require('child_process');
const {existsSync} = require('fs');
@@ -10,13 +10,13 @@ const {exec} = require('child_process');
const {join} = require('path');
const run = async () => {
const {getBuildInfo, updateVersionsForCanary} = require('./utils');
const {getBuildInfo, updateVersionsForNext} = require('./utils');
const cwd = join(__dirname, '..', '..');
const {reactVersion, version} = await getBuildInfo();
await updateVersionsForCanary(cwd, reactVersion, version);
await updateVersionsForNext(cwd, reactVersion, version);
};
// Install (or update) release script dependencies before proceeding.
@@ -12,7 +12,7 @@ const run = async () => {
console.log(
theme.caution(
'This script does not run any automated tests.' +
'You should run them manually before creating a canary release.'
'You should run them manually before creating a "next" release.'
)
);
@@ -2,12 +2,12 @@
'use strict';
const {logPromise, updateVersionsForCanary} = require('../utils');
const {logPromise, updateVersionsForNext} = require('../utils');
const theme = require('../theme');
module.exports = async ({reactVersion, tempDirectory, version}) => {
return logPromise(
updateVersionsForCanary(tempDirectory, reactVersion, version),
updateVersionsForNext(tempDirectory, reactVersion, version),
theme`Updating version numbers ({version ${version}})`
);
};
@@ -8,18 +8,18 @@ const {getBuildInfo, handleError} = require('./utils');
// This script is an escape hatch!
// It exists for special case manual builds.
// The typical suggested release process is to create a canary from a CI artifact.
// The typical suggested release process is to create a "next" build from a CI artifact.
// This build script is optimized for speed and simplicity.
// It doesn't run all of the tests that the CI environment runs.
// You're expected to run those manually before publishing a release.
const addBuildInfoJSON = require('./create-canary-commands/add-build-info-json');
const buildArtifacts = require('./create-canary-commands/build-artifacts');
const confirmAutomatedTesting = require('./create-canary-commands/confirm-automated-testing');
const copyRepoToTempDirectory = require('./create-canary-commands/copy-repo-to-temp-directory');
const npmPackAndUnpack = require('./create-canary-commands/npm-pack-and-unpack');
const addBuildInfoJSON = require('./create-next-commands/add-build-info-json');
const buildArtifacts = require('./create-next-commands/build-artifacts');
const confirmAutomatedTesting = require('./create-next-commands/confirm-automated-testing');
const copyRepoToTempDirectory = require('./create-next-commands/copy-repo-to-temp-directory');
const npmPackAndUnpack = require('./create-next-commands/npm-pack-and-unpack');
const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
const updateVersionNumbers = require('./create-canary-commands/update-version-numbers');
const updateVersionNumbers = require('./create-next-commands/update-version-numbers');
const run = async () => {
try {
@@ -28,7 +28,7 @@ const run = async ({cwd, local, packages, version}) => {
const nodeModulesPath = join(cwd, 'build/node_modules');
// Checkout canary release from NPM for all local packages
// Checkout "next" release from NPM for all local packages
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
@@ -55,6 +55,6 @@ const run = async ({cwd, local, packages, version}) => {
module.exports = async params => {
return logPromise(
run(params),
theme`Checking out canary from NPM {version ${params.version}}`
theme`Checking out "next" from NPM {version ${params.version}}`
);
};
@@ -5,11 +5,11 @@
const {execRead, logPromise} = require('../utils');
const run = async () => {
const version = await execRead('npm info react@canary version');
const version = await execRead('npm info react@next version');
return version;
};
module.exports = async params => {
return logPromise(run(params), 'Determining latest canary release version');
return logPromise(run(params), 'Determining latest "next" release version');
};
@@ -29,7 +29,7 @@ const paramDefinitions = [
{
name: 'version',
type: String,
description: 'Version of published canary release (e.g. 0.0.0-ddaf2b07c)',
description: 'Version of published "next" release (e.g. 0.0.0-ddaf2b07c)',
},
];
@@ -47,7 +47,7 @@ const run = async ({cwd, packages, version}, versionsMap) => {
// (e.g. scheduler@^0.11.0 becomes scheduler@^0.12.0 when we release scheduler 0.12.0).
// Otherwise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases).
// Note that in both cases, we must update the target package JSON,
// since canary releases are all locked to the canary version (e.g. 0.0.0-ddaf2b07c).
// since "next" releases are all locked to the version (e.g. 0.0.0-ddaf2b07c).
if (
sourceDependencyVersion ===
sourceDependencyConstraint.replace(/^[\^\~]/, '')
@@ -69,7 +69,7 @@ const run = async ({cwd, packages, version}, versionsMap) => {
// Update all package JSON versions and their dependencies/peerDependencies.
// This must be done in a way that respects semver constraints (e.g. 16.7.0, ^16.7.0, ^16.0.0).
// To do this, we use the dependencies defined in the source package JSONs,
// because the canary dependencies have already been flattened to an exact match (e.g. 0.0.0-ddaf2b07c).
// because the "next" dependencies have already been flattened to an exact match (e.g. 0.0.0-ddaf2b07c).
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
@@ -114,14 +114,14 @@ const run = async ({cwd, packages, version}, versionsMap) => {
// A separate "React version" is used for the embedded renderer version to support DevTools,
// since it needs to distinguish between different version ranges of React.
// We need to replace it as well as the canary version number.
// We need to replace it as well as the "next" version number.
const buildInfoPath = join(nodeModulesPath, 'react', 'build-info.json');
const {reactVersion} = await readJson(buildInfoPath);
if (!reactVersion) {
console.error(
theme`{error Unsupported or invalid build metadata in} {path build/node_modules/react/build-info.json}` +
theme`{error . This could indicate that you have specified an outdated canary version.}`
theme`{error . This could indicate that you have specified an outdated "next" version.}`
);
process.exit(1);
}
@@ -146,7 +146,7 @@ const run = async ({cwd, packages, version}, versionsMap) => {
const newStableVersion = versionsMap.get(packageName);
const beforeContents = readFileSync(path, 'utf8', {cwd});
let afterContents = beforeContents;
// Replace all canary version numbers (e.g. header @license).
// Replace all "next" version numbers (e.g. header @license).
while (afterContents.indexOf(version) >= 0) {
afterContents = afterContents.replace(version, newStableVersion);
}
+2 -2
View File
@@ -7,7 +7,7 @@ const {getPublicPackages, handleError} = require('./utils');
const checkOutPackages = require('./prepare-stable-commands/check-out-packages');
const confirmStableVersionNumbers = require('./prepare-stable-commands/confirm-stable-version-numbers');
const getLatestCanaryVersion = require('./prepare-stable-commands/get-latest-canary-version');
const getLatestNextVersion = require('./prepare-stable-commands/get-latest-next-version');
const guessStableVersionNumbers = require('./prepare-stable-commands/guess-stable-version-numbers');
const parseParams = require('./prepare-stable-commands/parse-params');
const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
@@ -28,7 +28,7 @@ const run = async () => {
const versionsMap = new Map();
if (!params.version) {
params.version = await getLatestCanaryVersion();
params.version = await getLatestNextVersion();
}
if (params.version.includes('experimental')) {
@@ -28,9 +28,9 @@ const run = async ({cwd, packages, tags}) => {
clear();
if (tags.length === 1 && tags[0] === 'canary') {
if (tags.length === 1 && tags[0] === 'next') {
console.log(
theme`{header A canary release} {version ${version}} {header has been published!}`
theme`{header A "next" release} {version ${version}} {header has been published!}`
);
} else {
const nodeModulesPath = join(cwd, 'build/node_modules');
@@ -7,7 +7,7 @@ const {join} = require('path');
const theme = require('../theme');
const run = async ({cwd, packages, tags}) => {
// Prevent a canary release from ever being published as @latest
// Prevent a "next" release from ever being published as @latest
// All canaries share a version number, so it's okay to check any of them.
const arbitraryPackageName = packages[0];
const packageJSONPath = join(
@@ -21,14 +21,14 @@ const run = async ({cwd, packages, tags}) => {
if (version.indexOf('0.0.0') === 0) {
if (tags.includes('latest')) {
console.log(
theme`{error Canary release} {version ${version}} {error cannot be tagged as} {tag latest}`
theme`{error Next release} {version ${version}} {error cannot be tagged as} {tag latest}`
);
process.exit(1);
}
} else {
if (tags.includes('canary')) {
if (tags.includes('next')) {
console.log(
theme`{error Stable release} {version ${version}} {error cannot be tagged as} {tag canary}`
theme`{error Stable release} {version ${version}} {error cannot be tagged as} {tag next}`
);
process.exit(1);
}
@@ -28,7 +28,7 @@ module.exports = ({cwd}, isStableRelease) => {
`;
} else {
message = theme`
{caution A canary release candidate has been prepared!}
{caution A "next" release candidate has been prepared!}
You can review the contents of this release in {path build/node_modules/}
+3 -3
View File
@@ -25,18 +25,18 @@ const run = async () => {
// https://circleci.com/gh/facebook/react/12707
let promise = spawn(
'node',
['./scripts/release/prepare-canary.js', `--build=${CIRCLE_CI_BUILD}`],
['./scripts/release/prepare-next.js', `--build=${CIRCLE_CI_BUILD}`],
defaultOptions
);
logPromise(
promise,
theme`Checking out canary build {version ${CIRCLE_CI_BUILD}}`
theme`Checking out "next" build {version ${CIRCLE_CI_BUILD}}`
);
await promise;
// Upgrade the above build top a known React version.
// Note that using the --local flag skips NPM checkout.
// This isn't totally necessary but is useful if we want to test an unpublished canary.
// This isn't totally necessary but is useful if we want to test an unpublished "next" build.
promise = spawn(
'node',
[
+7 -7
View File
@@ -92,7 +92,7 @@ const getBuildInfo = async () => {
const buildNumber = process.env.CIRCLE_BUILD_NUM;
// React version is stored explicitly, separately for DevTools support.
// See updateVersionsForCanary() below for more info.
// See updateVersionsForNext() below for more info.
const packageJSON = await readJson(
join(cwd, 'packages', 'react', 'package.json')
);
@@ -193,12 +193,12 @@ const splitCommaParams = array => {
// This method is used by both local Node release scripts and Circle CI bash scripts.
// It updates version numbers in package JSONs (both the version field and dependencies),
// As well as the embedded renderer version in "packages/shared/ReactVersion".
// Canaries version numbers use the format of 0.0.0-<sha> to be easily recognized (e.g. 0.0.0-57239eac8).
// Canaries version numbers use the format of 0.0.0-<sha> to be easily recognized (e.g. 0.0.0-01974a867).
// A separate "React version" is used for the embedded renderer version to support DevTools,
// since it needs to distinguish between different version ranges of React.
// It is based on the version of React in the local package.json (e.g. 16.6.1-canary-57239eac8).
// Both numbers will be replaced if the canary is promoted to a stable release.
const updateVersionsForCanary = async (cwd, reactVersion, version) => {
// It is based on the version of React in the local package.json (e.g. 16.12.0-01974a867).
// Both numbers will be replaced if the "next" release is promoted to a stable release.
const updateVersionsForNext = async (cwd, reactVersion, version) => {
const packages = getPublicPackages(join(cwd, 'packages'));
const packagesDir = join(cwd, 'packages');
@@ -234,7 +234,7 @@ const updateVersionsForCanary = async (cwd, reactVersion, version) => {
packageJSON.version = version;
// Also update inter-package dependencies.
// Canary releases always have exact version matches.
// Next releases always have exact version matches.
// The promote script may later relax these (e.g. "^x.x.x") based on source package JSONs.
const {dependencies, peerDependencies} = packageJSON;
for (let j = 0; j < packages.length; j++) {
@@ -263,5 +263,5 @@ module.exports = {
printDiff,
splitCommaParams,
theme,
updateVersionsForCanary,
updateVersionsForNext,
};