Publish a local release (canary or stable) to NPM (#14260)

New release scripts.

Learn more at https://github.com/facebook/react/blob/master/scripts/release/README.md
This commit is contained in:
Brian Vaughn 2018-11-23 12:37:18 -08:00 committed by GitHub
parent 7475120ce7
commit 686f1060ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 1731 additions and 1274 deletions

View File

@ -42,4 +42,7 @@ jobs:
- node_modules
- store_artifacts:
path: ./node_modules.tgz
path: ./node_modules.tgz
- store_artifacts:
path: ./scripts/error-codes/codes.json

View File

@ -12,7 +12,7 @@
},
"scripts": {
"prestart":
"cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/ && cp ../../build/dist/react-dom-server.browser.development.js public/",
"cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js public/",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",

View File

@ -18,7 +18,7 @@
},
"scripts": {
"start": "react-scripts start",
"prestart": "cp ../../build/dist/react.development.js ../../build/dist/react-dom.development.js ../../build/dist/react.production.min.js ../../build/dist/react-dom.production.min.js ../../build/dist/react-dom-server.browser.development.js ../../build/dist/react-dom-server.browser.production.min.js public/",
"prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/",
"build": "react-scripts build && cp build/index.html build/200.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"

View File

@ -9,7 +9,7 @@
},
"scripts": {
"prestart":
"cp ../../build/dist/react.development.js public/ && cp ../../build/dist/react-dom.development.js public/",
"cp ../../build/node_modules/react/umd/react.development.js public/ && cp ../../build/node_modules/react-dom/umd/react-dom.development.js public/",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",

View File

@ -16,8 +16,8 @@
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
</p>
</div>
<script src="../../build/dist/react.development.js"></script>
<script src="../../build/dist/react-dom.development.js"></script>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
var dotStyle = {

View File

@ -1,7 +1,7 @@
<html>
<body>
<script src="../../../build/dist/react.development.js"></script>
<script src="../../../build/dist/react-dom.development.js"></script>
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<div id="container"></div>
<script type="text/babel">

View File

@ -1,7 +1,7 @@
<html>
<body>
<script src="../../../build/dist/react.development.js"></script>
<script src="../../../build/dist/react-dom.development.js"></script>
<script src="../../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
<div id="container"></div>
<script>
ReactDOM.render(

View File

@ -1,7 +1,7 @@
<html>
<body>
<script src="../../../build/dist/react.production.min.js"></script>
<script src="../../../build/dist/react-dom.production.min.js"></script>
<script src="../../../build/node_modules/react/umd/react.production.min.js"></script>
<script src="../../../build/node_modules/react-dom/umd/react-dom.production.min.js"></script>
<div id="container"></div>
<script>
ReactDOM.render(

View File

@ -5,8 +5,8 @@
<script>
requirejs.config({
paths: {
react: '../../../build/dist/react.development',
'react-dom': '../../../build/dist/react-dom.development'
react: '../../../build/node_modules/react/umd/react.development',
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development'
}
});

View File

@ -5,8 +5,8 @@
<script>
requirejs.config({
paths: {
react: '../../../build/dist/react.production.min',
'react-dom': '../../../build/dist/react-dom.production.min'
react: '../../../build/node_modules/react/umd/react.production.min',
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min'
}
});

View File

@ -4,8 +4,9 @@ module.exports = {
out: 'output.js',
optimize: 'none',
paths: {
react: '../../../../build/dist/react.development',
'react-dom': '../../../../build/dist/react-dom.development',
react: '../../../../build/node_modules/react/umd/react.development',
'react-dom':
'../../../../build/node_modules/react-dom/umd/react-dom.development',
schedule: '../../../../build/dist/schedule.development',
},
};

View File

@ -4,8 +4,9 @@ module.exports = {
out: 'output.js',
optimize: 'none',
paths: {
react: '../../../../build/dist/react.production.min',
'react-dom': '../../../../build/dist/react-dom.production.min',
react: '../../../../build/node_modules/react/umd/react.production.min',
'react-dom':
'../../../../build/node_modules/react-dom/umd/react-dom.production.min',
schedule: '../../../../build/dist/schedule.development',
},
};

View File

@ -1,7 +1,8 @@
System.config({
paths: {
react: '../../../../build/dist/react.development.js',
'react-dom': '../../../../build/dist/react-dom.development.js',
react: '../../../../build/node_modules/react/umd/react.development.js',
'react-dom':
'../../../../build/node_modules/react-dom/umd/react-dom.development.js',
schedule: '../../../../build/dist/schedule.development',
},
});

View File

@ -1,7 +1,8 @@
System.config({
paths: {
react: '../../../../build/dist/react.production.min.js',
'react-dom': '../../../../build/dist/react-dom.production.min.js',
react: '../../../../build/node_modules/react/umd/react.production.min.js',
'react-dom':
'../../../../build/node_modules/react-dom/umd/react-dom.production.min.js',
schedule: '../../../../build/dist/schedule.development',
},
});

View File

@ -5,8 +5,8 @@
<script>
System.config({
paths: {
react: '../../../build/dist/react.development.js',
'react-dom': '../../../build/dist/react-dom.development.js'
react: '../../../build/node_modules/react/umd/react.development.js',
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.development.js'
}
});

View File

@ -5,8 +5,8 @@
<script>
System.config({
paths: {
react: '../../../build/dist/react.production.min.js',
'react-dom': '../../../build/dist/react-dom.production.min.js'
react: '../../../build/node_modules/react/umd/react.production.min.js',
'react-dom': '../../../build/node_modules/react-dom/umd/react-dom.production.min.js'
}
});

View File

@ -91,7 +91,7 @@
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
</ol>
<script src="../../build/dist/react.development.js"></script>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">

View File

@ -93,7 +93,7 @@
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
},
"scripts": {
"build": "npm run version-check && node ./scripts/rollup/build.js",
"build": "node ./scripts/rollup/build.js",
"linc": "node ./scripts/tasks/linc.js",
"lint": "node ./scripts/tasks/eslint.js",
"lint-build": "node ./scripts/rollup/validate/index.js",

View File

@ -6,6 +6,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/"
],

View File

@ -7,6 +7,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs"
],

View File

@ -22,6 +22,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/"
]

View File

@ -31,6 +31,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/",
"umd/",

View File

@ -7,6 +7,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/",
"umd/"

View File

@ -12,6 +12,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/"
],

View File

@ -24,6 +24,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"profiling.js",
"server.js",

View File

@ -15,6 +15,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/",
"umd/"

View File

@ -18,6 +18,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"persistent.js",
"cjs/"

View File

@ -11,6 +11,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"persistent.js",
"reflection.js",

View File

@ -26,6 +26,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"shallow.js",
"cjs/",

View File

@ -11,6 +11,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"cjs/",
"umd/"

View File

@ -19,6 +19,7 @@
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"tracing.js",
"tracing-profiling.js",

View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
node ./scripts/release/ci-add-build-info-json.js

View File

@ -25,6 +25,8 @@ if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
fi
if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
COMMANDS_TO_RUN+=('./scripts/circleci/add_build_info_json.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/update_package_versions.sh')
COMMANDS_TO_RUN+=('./scripts/circleci/build.sh')
COMMANDS_TO_RUN+=('yarn test-build --maxWorkers=2')
COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2')

View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
node ./scripts/release/ci-update-package-versions.js

View File

@ -1,13 +1,66 @@
# React Release Scripts
At a high-level, the release process uses two scripts: **build** and **publish**.
1. The **build** script does the heavy lifting (e.g., checking CI, running automated tests, building Rollup bundles) and then prints instructions for manual verification.
1. The **publish** script then publishes the built artifacts to npm and pushes to GitHub.
The release process consists of several phases, each one represented by one of the scripts below.
Run either script without parameters to see its usage, e.g.:
```
./scripts/release/build.js
./scripts/release/publish.js
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 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 using the [`prepare-stable`](#prepare-stable) and [`publish`](#publish) scripts. (This process is always manual.)
One or more release scripts are used for each of the above phases. Learn more about these scripts below:
* [`create-canary`](#create-canary)
* [`prepare-canary`](#prepare-canary)
* [`prepare-stable`](#prepare-stable)
* [`publish`](#publish)
## `create-canary`
Creates a canary 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.
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:
```sh
scripts/release/create-canary.js
```
Each script will guide the release engineer through any necessary steps (including environment setup and manual testing steps).
## `prepare-canary`
Downloads build artifacts from Circle CI in preparation to be published to NPM as a canary 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.
#### 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:
```sh
scripts/release/prepare-canary.js --build=12677
```
## `prepare-stable`
Checks out a canary 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.
#### Example usage
To promote the canary release `0.0.0-5bf84d292` (aka commit [5bf84d292](https://github.com/facebook/react/commit/5bf84d292)) to stable:
```sh
scripts/release/prepare-stable.js --version=0.0.0-5bf84d292
```
## `publish`
Publishes the current contents of `build/node_modules` to NPM.
This script publishes each public package to NPM and updates the specified tag(s) to match. **It does not test or verify the local package contents before publishing**. This should be done by the release engineer prior to running the script.
Upon completion, this script provides instructions for tagging the Git commit that the package was created from and updating the release CHANGELOG.
**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.
#### Example usage
To publish a release to NPM as both `next` and `latest`:
```sh
scripts/release/publish.js --tags next latest
```

View File

@ -1,23 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {execUnlessDry, logPromise} = require('../utils');
const run = async ({cwd, dry, version}) => {
await execUnlessDry(
`git tag -a v${version} -m "Tagging ${version} release"`,
{
cwd,
dry,
}
);
};
module.exports = async ({cwd, dry, version}) => {
return logPromise(
run({cwd, dry, version}),
`Creating git tag ${chalk.yellow.bold(`v${version}`)}`
);
};

View File

@ -1,32 +0,0 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {execRead, execUnlessDry, logPromise} = require('../utils');
const run = async ({cwd, dry, version}) => {
await exec('yarn build -- --extract-errors', {cwd});
const modifiedFiles = await execRead('git ls-files -m', {cwd});
if (modifiedFiles.includes('scripts/error-codes/codes.json')) {
await execUnlessDry('git add scripts/error-codes/codes.json', {cwd, dry});
await execUnlessDry(
`git commit -m "Update error codes for ${version} release"`,
{cwd, dry}
);
}
if (modifiedFiles.includes('scripts/rollup/results.json')) {
await execUnlessDry('git add scripts/rollup/results.json', {cwd, dry});
await execUnlessDry(
`git commit -m "Update bundle sizes for ${version} release"`,
{cwd, dry}
);
}
};
module.exports = async params => {
return logPromise(run(params), 'Building artifacts', true);
};

View File

@ -1,70 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const http = require('request-promise-json');
const logUpdate = require('log-update');
const prompt = require('prompt-promise');
const {execRead, logPromise} = require('../utils');
// https://circleci.com/docs/api/v1-reference/#projects
const CIRCLE_CI_BASE_URL =
'https://circleci.com/api/v1.1/project/github/facebook/react/tree/master';
const check = async ({cwd}) => {
const token = process.env.CIRCLE_CI_API_TOKEN;
const uri = `${CIRCLE_CI_BASE_URL}?circle-token=${token}&limit=1`;
const response = await http.get(uri, true);
const {outcome, status, vcs_revision: ciRevision} = response[0];
const gitRevision = await execRead('git rev-parse HEAD', {cwd});
if (gitRevision !== ciRevision) {
throw Error(
chalk`
CircleCI is stale
{white The latest Git revision is {yellow.bold ${gitRevision}}}
{white The most recent CircleCI revision is {yellow.bold ${ciRevision}}}
{white Please wait for CircleCI to catch up.}
`
);
} else if (outcome !== 'success') {
throw Error(
chalk`
CircleCI failed
{white The most recent CircleCI build has a status of {red.bold ${outcome ||
status}}}
{white Please retry this build in CircleCI if you believe this is an error.}
`
);
}
};
module.exports = async params => {
if (params.local) {
return;
}
if (params.skipCI) {
logUpdate(
chalk.red`Are you sure you want to skip CI? (y for yes, n for no)`
);
const confirm = await prompt('');
logUpdate.done();
if (confirm === 'y') {
return;
} else {
throw Error(
chalk`
Cancelling release.
`
);
}
}
return logPromise(check(params), 'Checking CircleCI status');
};

View File

@ -1,27 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
module.exports = () => {
if (!process.env.CIRCLE_CI_API_TOKEN) {
throw Error(
chalk`
{red Missing CircleCI API token}
{white The CircleCI API is used to check the status of the latest commit.}
{white This API requires a token which must be exposed via a {yellow.bold CIRCLE_CI_API_TOKEN} environment var.}
{white In order to run this script you will need to create your own API token.}
{white Instructions can be found at:}
{blue.bold https://circleci.com/docs/api/v1-reference/#getting-started}
{white To make this token available to the release script, add it to your {yellow.bold .bash_profile} like so:}
{gray # React release script}
{white export CIRCLE_CI_API_TOKEN=<your-token-here>}
`
);
}
};

View File

@ -1,60 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {readJson} = require('fs-extra');
const {join} = require('path');
const {dependencies} = require('../config');
const {logPromise} = require('../utils');
const check = async ({cwd, packages}) => {
const rootPackage = await readJson(join(cwd, 'package.json'));
const projectPackages = [];
for (let i = 0; i < packages.length; i++) {
const project = packages[i];
projectPackages.push(
await readJson(join(cwd, join('packages', project), 'package.json'))
);
}
const invalidDependencies = [];
const checkModule = module => {
const rootVersion = rootPackage.devDependencies[module];
projectPackages.forEach(projectPackage => {
// Not all packages have dependencies (eg react-is)
const projectVersion = projectPackage.dependencies
? projectPackage.dependencies[module]
: undefined;
if (rootVersion !== projectVersion && projectVersion !== undefined) {
invalidDependencies.push(
`${module} is ${chalk.red.bold(rootVersion)} in root but ` +
`${chalk.red.bold(projectVersion)} in ${projectPackage.name}`
);
}
});
};
dependencies.forEach(checkModule);
if (invalidDependencies.length) {
throw Error(
chalk`
Dependency mismatch
{white The following dependencies do not match between the root package and NPM dependencies:}
${invalidDependencies
.map(dependency => chalk.white(dependency))
.join('\n')}
`
);
}
};
module.exports = async params => {
return logPromise(check(params), 'Checking runtime dependencies');
};

View File

@ -1,20 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {execRead} = require('../utils');
module.exports = async ({cwd}) => {
const status = await execRead('git diff HEAD', {cwd});
if (status) {
throw Error(
chalk`
Uncommitted local changes
{white Please revert or commit all local changes before making a release.}
`
);
}
};

View File

@ -1,15 +0,0 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {logPromise} = require('../utils');
const install = async ({cwd}) => {
await exec('rm -rf node_modules', {cwd});
await exec('yarn', {cwd});
};
module.exports = async ({cwd}) => {
return logPromise(install({cwd}), 'Installing NPM dependencies');
};

View File

@ -1,57 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const figlet = require('figlet');
const {paramDefinitions} = require('../config');
module.exports = () => {
const params = commandLineArgs(paramDefinitions);
if (!params.version) {
const usage = commandLineUsage([
{
content: chalk
.hex('#61dafb')
.bold(figlet.textSync('react', {font: 'Graffiti'})),
raw: true,
},
{
content: 'Automated pre-release build script.',
},
{
header: 'Options',
optionList: paramDefinitions,
},
{
header: 'Examples',
content: [
{
desc: '1. A concise example.',
example: '$ ./build.js [bold]{-v} [underline]{16.0.0}',
},
{
desc: '2. Dry run build a release candidate (no git commits).',
example:
'$ ./build.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}',
},
{
desc: '3. Release from another checkout.',
example:
'$ ./build.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo',
},
],
},
]);
console.log(usage);
process.exit(1);
}
return {
...params,
cwd: params.path, // For script convenience
};
};

View File

@ -1,55 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {join, relative} = require('path');
const {getUnexecutedCommands} = require('../utils');
const CHANGELOG_PATH =
'https://github.com/facebook/react/edit/master/CHANGELOG.md';
module.exports = ({cwd, dry, path, version}) => {
const publishPath = relative(
process.env.PWD,
join(__dirname, '../publish.js')
);
const command =
`${publishPath} -v ${version}` +
(path ? ` -p ${path}` : '') +
(dry ? ' --dry' : '');
const packagingFixturesPath = join(cwd, 'fixtures/packaging');
const standaloneFixturePath = join(
cwd,
'fixtures/packaging/babel-standalone/dev.html'
);
console.log(
chalk`
{green.bold Build successful!}
${getUnexecutedCommands()}
Next there are a couple of manual steps:
{bold.underline Step 1: Update the CHANGELOG}
Here are a few things to keep in mind:
The changes should be easy to understand. (Friendly one-liners are better than PR titles.)
Make sure all contributors are credited.
Verify that the markup is valid by previewing it in the editor: {blue.bold ${CHANGELOG_PATH}}
{bold.underline Step 2: Smoke test the packages}
1. Open {yellow.bold ${standaloneFixturePath}} in the browser.
2. It should say {italic "Hello world!"}
3. Next go to {yellow.bold ${packagingFixturesPath}} and run {bold node build-all.js}
4. Install the "serve" module ({bold npm install -g serve})
5. Go to the repo root and {bold serve -s .}
6. Open {blue.bold http://localhost:5000/fixtures/packaging}
7. Verify every iframe shows {italic "Hello world!"}
After completing the above steps, resume the release process by running:
{yellow.bold ${command}}
`.replace(/\n +/g, '\n')
);
};

View File

@ -1,30 +0,0 @@
#!/usr/bin/env node
'use strict';
const {logPromise, runYarnTask} = require('../utils');
module.exports = async ({cwd}) => {
await logPromise(
runYarnTask(cwd, 'lint-build', 'Lint bundle failed'),
'Running ESLint on bundle'
);
await logPromise(
runYarnTask(
cwd,
'test-build',
'Jest tests on the bundle failed in development'
),
'Running Jest tests on the bundle in the development environment',
true
);
await logPromise(
runYarnTask(
cwd,
'test-build-prod',
'Jest tests on the bundle failed in production'
),
'Running Jest tests on the bundle in the production environment',
true
);
};

View File

@ -1,23 +0,0 @@
#!/usr/bin/env node
'use strict';
const {logPromise, runYarnTask} = require('../utils');
module.exports = async ({cwd}) => {
await logPromise(runYarnTask(cwd, 'lint', 'Lint failed'), 'Running ESLint');
await logPromise(
runYarnTask(cwd, 'flow-ci', 'Flow failed'),
'Running Flow checks'
);
await logPromise(
runYarnTask(cwd, 'test', 'Jest tests failed in development'),
'Running Jest tests in the development environment',
true
);
await logPromise(
runYarnTask(cwd, 'test-prod', 'Jest tests failed in production'),
'Running Jest tests in the production environment',
true
);
};

View File

@ -1,26 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {exec} = require('child-process-promise');
const {logPromise} = require('../utils');
const update = async ({cwd, branch, local}) => {
if (!local) {
await exec('git fetch', {cwd});
}
await exec(`git checkout ${branch}`, {cwd});
if (!local) {
await exec('git pull', {cwd});
}
};
module.exports = async params => {
return logPromise(
update(params),
`Updating checkout ${chalk.yellow.bold(
params.cwd
)} on branch ${chalk.yellow.bold(params.branch)}`
);
};

View File

@ -1,47 +0,0 @@
#!/usr/bin/env node
'use strict';
const {readJson, writeJson} = require('fs-extra');
const {join} = require('path');
const semver = require('semver');
const {execRead, execUnlessDry, logPromise} = require('../utils');
const getReactReconcilerVersion = async cwd => {
const path = join(cwd, 'packages', 'react-reconciler', 'package.json');
const json = await readJson(path);
return json.version;
};
const update = async ({cwd, dry}) => {
const path = join(cwd, 'packages', 'react-noop-renderer', 'package.json');
const json = await readJson(path);
// IMPORTANT: This script must be run after update-package-versions,
// Since it depends up the updated react-reconciler version.
const reconcilerVersion = await getReactReconcilerVersion(cwd);
// There is no wildcard for semver that includes prerelease ranges as well.
// This causes problems for our Yarn workspaces setup,
// Since the noop-renderer depends on react-reconciler.
// So we have a special case check for this that ensures semver compatibility.
if (semver.prerelease(reconcilerVersion)) {
json.dependencies['react-reconciler'] = `* || ${reconcilerVersion}`;
} else {
json.dependencies['react-reconciler'] = '*';
}
await writeJson(path, json, {spaces: 2});
const status = await execRead('git status -s', {cwd});
if (status) {
await execUnlessDry(
`git commit -am "Updating dependencies for react-noop-renderer"`,
{cwd, dry}
);
}
};
module.exports = async params => {
return logPromise(update(params), 'Updating noop renderer dependencies');
};

View File

@ -1,153 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {exec} = require('child-process-promise');
const {readFileSync, writeFileSync} = require('fs');
const {readJson, writeJson} = require('fs-extra');
const {join} = require('path');
const semver = require('semver');
const {execUnlessDry, logPromise} = require('../utils');
const getNextVersion = (prevVersion, releaseVersion) => {
const prerelease = semver.prerelease(releaseVersion);
// Unstable packages (eg version < 1.0) are treated specially:
// Rather than use the release version (eg 16.1.0)-
// We just auto-increment the minor version (eg 0.1.0 -> 0.2.0).
// If we're doing a prerelease, we also append the suffix (eg 0.2.0-beta).
if (semver.lt(prevVersion, '1.0.0')) {
let suffix = '';
if (prerelease) {
suffix = `-${prerelease.join('.')}`;
}
// If this is a new pre-release, increment the minor.
// Else just increment (or remove) the pre-release suffix.
// This way our minor version isn't incremented unnecessarily with each prerelease.
const minor = semver.prerelease(prevVersion)
? semver.minor(prevVersion)
: semver.minor(prevVersion) + 1;
return `0.${minor}.0${suffix}`;
} else {
return releaseVersion;
}
};
const update = async ({cwd, dry, packages, version}) => {
try {
// Update root package.json
const packagePath = join(cwd, 'package.json');
const rootPackage = await readJson(packagePath);
rootPackage.version = version;
await writeJson(packagePath, rootPackage, {spaces: 2});
// Update ReactVersion source file
const reactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
const reactVersion = readFileSync(reactVersionPath, 'utf8').replace(
/module\.exports = '[^']+';/,
`module.exports = '${version}';`
);
writeFileSync(reactVersionPath, reactVersion);
// Update renderer versions and peer dependencies
const updateProjectPackage = async project => {
const path = join(cwd, 'packages', project, 'package.json');
const json = await readJson(path);
const prerelease = semver.prerelease(version);
// If this is a package we publish directly to NPM, update its version.
// Skip ones that we don't directly publish though (e.g. react-native-renderer).
if (json.private !== true) {
json.version = getNextVersion(json.version, version);
}
if (project === 'react') {
// Update inter-package dependencies as well.
// e.g. react depends on scheduler
if (json.dependencies) {
Object.keys(json.dependencies).forEach(dependency => {
if (packages.indexOf(dependency) >= 0) {
const prevVersion = json.dependencies[dependency];
const nextVersion = getNextVersion(
prevVersion.replace('^', ''),
version
);
json.dependencies[dependency] = `^${nextVersion}`;
}
});
}
} else if (json.peerDependencies) {
let peerVersion = json.peerDependencies.react.replace('^', '');
// If the previous release was a pre-release version,
// The peer dependency will contain multiple versions, eg "^16.0.0 || 16.3.0-alpha.0"
// In this case, assume the first one will be the major and extract it.
if (peerVersion.includes(' || ')) {
peerVersion = peerVersion.split(' || ')[0];
}
// Release engineers can manually update minor and bugfix versions,
// But we should ensure that major versions always match.
if (semver.major(version) !== semver.major(peerVersion)) {
json.peerDependencies.react = `^${semver.major(version)}.0.0`;
} else {
json.peerDependencies.react = `^${peerVersion}`;
}
// If this is a prerelease, update the react dependency as well.
// A dependency on a major version won't satisfy a prerelease version.
// So rather than eg "^16.0.0" we need "^16.0.0 || 16.3.0-alpha.0"
if (prerelease) {
json.peerDependencies.react += ` || ${version}`;
}
// Update inter-package dependencies as well.
// e.g. react-test-renderer depends on react-is
if (json.dependencies) {
Object.keys(json.dependencies).forEach(dependency => {
if (packages.indexOf(dependency) >= 0) {
const prevVersion = json.dependencies[dependency];
// Special case to handle e.g. react-noop-renderer
if (prevVersion === '*') {
return;
}
const nextVersion = getNextVersion(
prevVersion.replace('^', ''),
version
);
json.dependencies[dependency] = `^${nextVersion}`;
}
});
}
}
await writeJson(path, json, {spaces: 2});
};
await Promise.all(packages.map(updateProjectPackage));
// Version sanity check
await exec('yarn version-check', {cwd});
await execUnlessDry(
`git commit -am "Updating package versions for release ${version}"`,
{cwd, dry}
);
} catch (error) {
throw Error(
chalk`
Failed while updating package versions
{white ${error.message}}
`
);
}
};
module.exports = async params => {
return logPromise(update(params), 'Updating package versions');
};

View File

@ -1,38 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {exec} = require('child-process-promise');
const {dependencies} = require('../config');
const {execRead, execUnlessDry, logPromise} = require('../utils');
const update = async ({cwd, dry, version}) => {
await exec(`yarn upgrade ${dependencies.join(' ')}`, {cwd});
const modifiedFiles = await execRead('git ls-files -m', {cwd});
// If yarn.lock has changed we should commit it.
// If anything else has changed, it's an error.
if (modifiedFiles) {
if (modifiedFiles !== 'yarn.lock') {
throw Error(
chalk`
Unexpected modifications
{white The following files have been modified unexpectedly:}
{gray ${modifiedFiles}}
`
);
}
await execUnlessDry(
`git commit -am "Updating yarn.lock file for ${version} release"`,
{cwd, dry}
);
}
};
module.exports = async params => {
return logPromise(update(params), 'Upgrading NPM dependencies');
};

View File

@ -1,20 +0,0 @@
'use strict';
const chalk = require('chalk');
const {readJson} = require('fs-extra');
const {join} = require('path');
const semver = require('semver');
module.exports = async ({cwd, version}) => {
if (!semver.valid(version)) {
throw Error('Invalid version specified');
}
const rootPackage = await readJson(join(cwd, 'package.json'));
if (!semver.gt(version, rootPackage.version)) {
throw Error(
chalk`Version {white ${rootPackage.version}} has already been published`
);
}
};

View File

@ -1,82 +0,0 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child_process');
// Follows the steps outlined in github.com/facebook/react/issues/10620
const run = async () => {
const chalk = require('chalk');
const logUpdate = require('log-update');
const {getPublicPackages, getPackages} = require('./utils');
const addGitTag = require('./build-commands/add-git-tag');
const buildArtifacts = require('./build-commands/build-artifacts');
const checkCircleCiStatus = require('./build-commands/check-circle-ci-status');
const checkEnvironmentVariables = require('./build-commands/check-environment-variables');
const checkNpmPermissions = require('./build-commands/check-npm-permissions');
const checkPackageDependencies = require('./build-commands/check-package-dependencies');
const checkUncommittedChanges = require('./build-commands/check-uncommitted-changes');
const installYarnDependencies = require('./build-commands/install-yarn-dependencies');
const parseBuildParameters = require('./build-commands/parse-build-parameters');
const printPostBuildSummary = require('./build-commands/print-post-build-summary');
const runAutomatedTests = require('./build-commands/run-automated-tests');
const runAutomatedBundleTests = require('./build-commands/run-automated-bundle-tests');
const updateGit = require('./build-commands/update-git');
const updateNoopRendererDependencies = require('./build-commands/update-noop-renderer-dependencies');
const updatePackageVersions = require('./build-commands/update-package-versions');
const updateYarnDependencies = require('./build-commands/update-yarn-dependencies');
const validateVersion = require('./build-commands/validate-version');
try {
const params = parseBuildParameters();
params.packages = getPublicPackages();
await checkEnvironmentVariables(params);
await validateVersion(params);
await checkUncommittedChanges(params);
await checkNpmPermissions(params);
await updateGit(params);
await checkCircleCiStatus(params);
await installYarnDependencies(params);
await checkPackageDependencies(params);
await updateYarnDependencies(params);
await runAutomatedTests(params);
// Also update NPM dependencies for private packages (e.g. react-native-renderer)
// Even though we don't publish these to NPM,
// mismatching dependencies can cause `yarn install` to install duplicate packages.
await updatePackageVersions({
...params,
packages: getPackages(),
});
await updateNoopRendererDependencies(params);
await buildArtifacts(params);
await runAutomatedBundleTests(params);
await addGitTag(params);
await printPostBuildSummary(params);
} catch (error) {
logUpdate.clear();
const message = error.message.trim().replace(/\n +/g, '\n');
const stack = error.stack.replace(error.message, '');
console.log(
`${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(
stack
)}`
);
process.exit(1);
}
};
// Install (or update) release script dependencies before proceeding.
// This needs to be done before we require() the first NPM module.
exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
if (error) {
console.error(error);
process.exit(1);
} else {
run();
}
});

View File

@ -0,0 +1,72 @@
#!/usr/bin/env node
'use strict';
// This script is run by Circle CI (see ../scripts/circleci).
// It is not meant to be run as part of the local build or publish process.
// 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
const {exec} = require('child_process');
const {existsSync} = require('fs');
const {join} = require('path');
const run = async () => {
const {writeJson, readJson} = require('fs-extra');
const {getBuildInfo, getPublicPackages} = require('./utils');
const cwd = join(__dirname, '..', '..');
const {
branch,
buildNumber,
checksum,
commit,
reactVersion,
} = await getBuildInfo();
const packages = getPublicPackages(join(cwd, 'packages'));
const packagesDir = join(cwd, 'packages');
const buildInfoJSON = {
branch,
buildNumber,
checksum,
commit,
environment: 'ci',
reactVersion,
};
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packagePath = join(packagesDir, packageName);
const packageJSON = await readJson(join(packagePath, 'package.json'));
// Verify all public packages include "build-info.json" in the files array.
if (!packageJSON.files.includes('build-info.json')) {
console.error(
`${packageName} must include "build-info.json" in files array.`
);
process.exit(1);
}
// Add build info JSON to package.
if (existsSync(join(packagePath, 'npm'))) {
const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json');
await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2});
}
}
};
// Install (or update) release script dependencies before proceeding.
// This needs to be done before we require() the first NPM module.
exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
if (error) {
console.error(error);
process.exit(1);
} else {
run();
}
});

View File

@ -0,0 +1,31 @@
#!/usr/bin/env node
'use strict';
// This script is run by Circle CI (see ../scripts/circleci).
// It is not meant to be run as part of the local build or publish process.
// It exists to share code between the Node release scripts and CI bash scripts.
const {exec} = require('child_process');
const {join} = require('path');
const run = async () => {
const {getBuildInfo, updateVersionsForCanary} = require('./utils');
const cwd = join(__dirname, '..', '..');
const {reactVersion, version} = await getBuildInfo();
await updateVersionsForCanary(cwd, reactVersion, version);
};
// Install (or update) release script dependencies before proceeding.
// This needs to be done before we require() the first NPM module.
exec('yarn install', {cwd: __dirname}, (error, stdout, stderr) => {
if (error) {
console.error(error);
process.exit(1);
} else {
run();
}
});

View File

@ -1,56 +0,0 @@
'use strict';
const dependencies = ['object-assign', 'prop-types'];
const paramDefinitions = [
{
name: 'dry',
type: Boolean,
description: 'Build artifacts but do not commit or publish',
defaultValue: false,
},
{
name: 'path',
type: String,
alias: 'p',
description:
'Location of React repository to release; defaults to [bold]{cwd}',
defaultValue: '.',
},
{
name: 'version',
type: String,
alias: 'v',
description: 'Semantic version number',
},
{
name: 'branch',
type: String,
alias: 'b',
description: 'Branch to build from; defaults to [bold]{master}',
defaultValue: 'master',
},
{
name: 'local',
type: Boolean,
description:
"Don't push or pull changes from remote repo. Don't check CI status.",
},
{
name: 'tag',
type: String,
description:
'The npm dist tag; defaults to [bold]{latest} for a stable' +
'release, [bold]{next} for unstable',
},
{
name: 'skipCI',
type: Boolean,
description: 'Skip Circle CI status check (requires confirmation)',
},
];
module.exports = {
dependencies,
paramDefinitions,
};

View File

@ -0,0 +1,50 @@
#!/usr/bin/env node
'use strict';
// IMPORTANT:
// Changes below should be mirrored in ../ci-add-build-info-json.js
const {existsSync} = require('fs');
const {writeJson, readJson} = require('fs-extra');
const {join} = require('path');
const {getPublicPackages, logPromise} = require('../utils');
const theme = require('../theme');
const run = async ({branch, checksum, commit, reactVersion, tempDirectory}) => {
const packages = getPublicPackages(join(tempDirectory, 'packages'));
const packagesDir = join(tempDirectory, 'packages');
const buildInfoJSON = {
branch,
buildNumber: null,
checksum,
commit,
environment: 'local',
reactVersion,
};
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packagePath = join(packagesDir, packageName);
const packageJSON = await readJson(join(packagePath, 'package.json'));
// Verify all public packages include "build-info.json" in the files array.
if (!packageJSON.files.includes('build-info.json')) {
console.error(
theme`{error ${packageName} must include "build-info.json" in files array.}`
);
process.exit(1);
}
// Add build info JSON to package.
if (existsSync(join(packagePath, 'npm'))) {
const buildInfoJSONPath = join(packagePath, 'npm', 'build-info.json');
await writeJson(buildInfoJSONPath, buildInfoJSON, {spaces: 2});
}
}
};
module.exports = async params => {
return logPromise(run(params), 'Adding build metadata to packages');
};

View File

@ -0,0 +1,25 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {join} = require('path');
const {logPromise} = require('../utils');
const run = async ({cwd, dry, tempDirectory}) => {
const defaultOptions = {
cwd: tempDirectory,
};
await exec('yarn install', defaultOptions);
await exec('yarn build -- --extract-errors', defaultOptions);
const tempNodeModulesPath = join(tempDirectory, 'build', 'node_modules');
const buildPath = join(cwd, 'build');
await exec(`cp -r ${tempNodeModulesPath} ${buildPath}`);
};
module.exports = async params => {
return logPromise(run(params), 'Building artifacts', true);
};

View File

@ -0,0 +1,24 @@
#!/usr/bin/env node
'use strict';
const clear = require('clear');
const {confirm} = require('../utils');
const theme = require('../theme');
const run = async () => {
clear();
console.log(
theme.caution(
'This script does not run any automated tests.' +
'You should run them manually before creating a canary release.'
)
);
await confirm('Do you want to proceed?');
clear();
};
module.exports = run;

View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {join} = require('path');
const {tmpdir} = require('os');
const {logPromise} = require('../utils');
const theme = require('../theme');
const run = async ({commit, cwd, tempDirectory}) => {
const directory = `react-${commit}`;
const temp = tmpdir();
if (tempDirectory !== join(tmpdir(), directory)) {
throw Error(`Unexpected temporary directory "${tempDirectory}"`);
}
await exec(`rm -rf ${directory}`, {cwd: temp});
await exec(`git archive --format=tar --output=${temp}/react.tgz ${commit}`, {
cwd,
});
await exec(`mkdir ${directory}`, {cwd: temp});
await exec(`tar -xf ./react.tgz -C ./${directory}`, {cwd: temp});
};
module.exports = async params => {
return logPromise(
run(params),
theme`Copying React repo to temporary directory ({path ${
params.tempDirectory
}})`
);
};

View File

@ -0,0 +1,57 @@
#!/usr/bin/env node
'use strict';
const {join} = require('path');
const {exec} = require('child-process-promise');
const {readdirSync} = require('fs');
const {readJsonSync} = require('fs-extra');
const {logPromise} = require('../utils');
const run = async ({cwd, dry, tempDirectory}) => {
// Cleanup from previous build.
await exec(`rm -rf ./build`, {cwd});
// NPM pack all built packages.
// We do this to ensure that the package.json files array is correct.
const builtPackages = readdirSync(join(tempDirectory, 'build/node_modules/'));
for (let i = 0; i < builtPackages.length; i++) {
await exec(`npm pack ./${builtPackages[i]}`, {
cwd: `${tempDirectory}/build/node_modules/`,
});
}
await exec('mkdir build', {cwd});
await exec('mkdir build/node_modules', {cwd});
await exec(
`cp -r ${tempDirectory}/build/node_modules/*.tgz ./build/node_modules/`,
{cwd}
);
// Unpack packages and prepare to publish.
const compressedPackages = readdirSync(join(cwd, 'build/node_modules/'));
for (let i = 0; i < compressedPackages.length; i++) {
await exec(
`tar -zxvf ./build/node_modules/${
compressedPackages[i]
} -C ./build/node_modules/`,
{cwd}
);
const packageJSON = readJsonSync(
join(cwd, `./build/node_modules/package/package.json`)
);
await exec(
`mv ./build/node_modules/package ./build/node_modules/${
packageJSON.name
}`,
{cwd}
);
}
// Cleanup.
await exec(`rm ./build/node_modules/*.tgz`, {cwd});
};
module.exports = async params => {
return logPromise(run(params), 'Packing artifacts');
};

View File

@ -0,0 +1,13 @@
#!/usr/bin/env node
'use strict';
const {logPromise, updateVersionsForCanary} = require('../utils');
const theme = require('../theme');
module.exports = async ({reactVersion, tempDirectory, version}) => {
return logPromise(
updateVersionsForCanary(tempDirectory, reactVersion, version),
theme`Updating version numbers ({version ${version}})`
);
};

View File

@ -0,0 +1,57 @@
#!/usr/bin/env node
'use strict';
const {tmpdir} = require('os');
const {join} = require('path');
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.
// 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 printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
const updateVersionNumbers = require('./create-canary-commands/update-version-numbers');
const run = async () => {
try {
const cwd = join(__dirname, '..', '..');
const {
branch,
checksum,
commit,
reactVersion,
version,
} = await getBuildInfo();
const tempDirectory = join(tmpdir(), `react-${commit}`);
const params = {
branch,
checksum,
commit,
cwd,
reactVersion,
tempDirectory,
version,
};
await confirmAutomatedTesting(params);
await copyRepoToTempDirectory(params);
await updateVersionNumbers(params);
await addBuildInfoJSON(params);
await buildArtifacts(params);
await npmPackAndUnpack(params);
await printPrereleaseSummary(params);
} catch (error) {
handleError(error);
}
};
run();

View File

@ -7,10 +7,12 @@
"dependencies": {
"chalk": "^2.1.0",
"child-process-promise": "^2.2.1",
"clear": "^0.1.0",
"cli-spinners": "^1.1.0",
"command-line-args": "^4.0.7",
"command-line-usage": "^4.0.1",
"figlet": "^1.2.0",
"diff": "^3.5.0",
"folder-hash": "^2.1.2",
"fs-extra": "^4.0.2",
"log-update": "^2.1.0",
"prompt-promise": "^1.0.3",

View File

@ -0,0 +1,30 @@
#!/usr/bin/env node
'use strict';
const theme = require('../theme');
module.exports = () => {
if (!process.env.CIRCLE_CI_API_TOKEN) {
console.error(
theme`
{error Missing CircleCI API token}
The CircleCI API is used to download build artifacts.
This API requires a token which must be exposed via a {underline CIRCLE_CI_API_TOKEN} environment var.
In order to run this script you will need to create your own API token.
Instructions can be found at:
{link https://circleci.com/docs/api/v1-reference/#getting-started}
To make this token available to the release script, add it to your {path .bash_profile} like so:
{dimmed # React release script}
export CIRCLE_CI_API_TOKEN=<your-token-here>
`
.replace(/\n +/g, '\n')
.trim()
);
process.exit(1);
}
};

View File

@ -0,0 +1,61 @@
#!/usr/bin/env node
'use strict';
const http = require('request-promise-json');
const {exec} = require('child-process-promise');
const {readdirSync} = require('fs');
const {readJsonSync} = require('fs-extra');
const {join} = require('path');
const {logPromise} = require('../utils');
const theme = require('../theme');
const run = async ({build, cwd}) => {
// https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci
const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${build}/artifacts?circle-token=${
process.env.CIRCLE_CI_API_TOKEN
}`;
const metadata = await http.get(metadataURL, true);
const nodeModulesURL = metadata.find(
entry => entry.path === 'home/circleci/project/node_modules.tgz'
).url;
// Download and extract artifact
await exec(`rm -rf ./build/node_modules*`, {cwd});
await exec(`curl ${nodeModulesURL} --output ./build/node_modules.tgz`, {cwd});
await exec(`mkdir ./build/node_modules`, {cwd});
await exec(`tar zxvf ./build/node_modules.tgz -C ./build/node_modules/`, {
cwd,
});
// Unpack packages and prepare to publish
const compressedPackages = readdirSync(join(cwd, 'build/node_modules/'));
for (let i = 0; i < compressedPackages.length; i++) {
await exec(
`tar zxvf ./build/node_modules/${
compressedPackages[i]
} -C ./build/node_modules/`,
{cwd}
);
const packageJSON = readJsonSync(
join(cwd, `/build/node_modules/package/package.json`)
);
await exec(
`mv ./build/node_modules/package ./build/node_modules/${
packageJSON.name
}`,
{cwd}
);
}
// Cleanup
await exec(`rm ./build/node_modules.tgz`, {cwd});
await exec(`rm ./build/node_modules/*.tgz`, {cwd});
};
module.exports = async ({build, cwd}) => {
return logPromise(
run({build, cwd}),
theme`Downloading artifacts from Circle CI for build {build ${build}}`
);
};

View File

@ -0,0 +1,45 @@
#!/usr/bin/env node
'use strict';
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const paramDefinitions = [
{
name: 'build',
type: Number,
description:
'Circle CI build identifier (e.g. https://circleci.com/gh/facebook/react/<build>)',
},
];
module.exports = () => {
const params = commandLineArgs(paramDefinitions);
if (!params.build) {
const usage = commandLineUsage([
{
content:
'Prepare a Circle CI build to be published to NPM as a canary.',
},
{
header: 'Options',
optionList: paramDefinitions,
},
{
header: 'Examples',
content: [
{
desc: 'Example:',
example: '$ ./prepare-canary.js [bold]{--build=}[underline]{12639}',
},
],
},
]);
console.log(usage);
process.exit(1);
}
return params;
};

View File

@ -0,0 +1,27 @@
#!/usr/bin/env node
'use strict';
const {join} = require('path');
const {getPublicPackages, handleError} = require('./utils');
const checkEnvironmentVariables = require('./prepare-canary-commands/check-environment-variables');
const downloadBuildArtifacts = require('./prepare-canary-commands/download-build-artifacts');
const parseParams = require('./prepare-canary-commands/parse-params');
const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary');
const run = async () => {
try {
const params = parseParams();
params.cwd = join(__dirname, '..', '..');
params.packages = await getPublicPackages();
await checkEnvironmentVariables(params);
await downloadBuildArtifacts(params);
await printPrereleaseSummary(params);
} catch (error) {
handleError(error);
}
};
run();

View File

@ -0,0 +1,29 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {join} = require('path');
const {logPromise} = require('../utils');
const theme = require('../theme');
const run = async ({cwd, packages, version}) => {
// Cleanup from previous builds
await exec(`rm -rf ./build/node_modules*`, {cwd});
await exec(`mkdir ./build/node_modules`, {cwd});
const nodeModulesPath = join(cwd, 'build/node_modules');
// Checkout canary release from NPM for all local packages
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
await exec(`npm i ${packageName}@${version}`, {cwd: nodeModulesPath});
}
};
module.exports = async params => {
return logPromise(
run(params),
theme`Checking out canary from NPM {version ${params.version}}`
);
};

View File

@ -0,0 +1,58 @@
#!/usr/bin/env node
'use strict';
const prompt = require('prompt-promise');
const semver = require('semver');
const theme = require('../theme');
const run = async (params, versionsMap) => {
const groupedVersionsMap = new Map();
// Group packages with the same source versions.
// We want these to stay lock-synced anyway.
// This will require less redundant input from the user later,
// and reduce the likelihood of human error (entering the wrong version).
versionsMap.forEach((version, packageName) => {
if (!groupedVersionsMap.has(version)) {
groupedVersionsMap.set(version, [packageName]);
} else {
groupedVersionsMap.get(version).push(packageName);
}
});
// Prompt user to confirm or override each version group.
const entries = [...groupedVersionsMap.entries()];
for (let i = 0; i < entries.length; i++) {
const [bestGuessVersion, packages] = entries[i];
const packageNames = packages.map(name => theme.package(name)).join(', ');
const defaultVersion = bestGuessVersion
? theme.version(` (default ${bestGuessVersion})`)
: '';
const version =
(await prompt(
theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: `
)) || bestGuessVersion;
prompt.done();
// Verify a valid version has been supplied.
try {
semver(version);
packages.forEach(packageName => {
versionsMap.set(packageName, version);
});
} catch (error) {
console.log(
theme`{spinnerError ✘} Version {version ${version}} is invalid.`
);
// Prompt again
i--;
}
}
};
// Run this directly because it's fast,
// and logPromise would interfere with console prompting.
module.exports = run;

View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
'use strict';
const semver = require('semver');
const {execRead, logPromise} = require('../utils');
const run = async ({cwd, packages}, versionsMap) => {
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
try {
// In case local package JSONs are outdated,
// guess the next version based on the latest NPM release.
const version = await execRead(`npm show ${packageName} version`);
const {major, minor, patch} = semver(version);
// Guess the next version by incrementing patch.
// The script will confirm this later.
versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
} catch (error) {
// If the package has not yet been published,
// we'll require a version number to be entered later.
versionsMap.set(packageName, null);
}
}
};
module.exports = async (params, versionsMap) => {
return logPromise(
run(params, versionsMap),
'Guessing stable version numbers'
);
};

View File

@ -0,0 +1,44 @@
#!/usr/bin/env node
'use strict';
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const paramDefinitions = [
{
name: 'version',
type: String,
description: 'Version of published canary release (e.g. 0.0.0-ddaf2b07c)',
},
];
module.exports = () => {
const params = commandLineArgs(paramDefinitions);
if (!params.version) {
const usage = commandLineUsage([
{
content: 'Prepare a published canary release to be promoted to stable.',
},
{
header: 'Options',
optionList: paramDefinitions,
},
{
header: 'Examples',
content: [
{
desc: 'Example:',
example:
'$ ./prepare-stable.js [bold]{--version=}[underline]{0.0.0-ddaf2b07c}',
},
],
},
]);
console.log(usage);
process.exit(1);
}
return params;
};

View File

@ -0,0 +1,175 @@
#!/usr/bin/env node
'use strict';
const clear = require('clear');
const {readFileSync, writeFileSync} = require('fs');
const {readJson, writeJson} = require('fs-extra');
const {join, relative} = require('path');
const {confirm, execRead, printDiff} = require('../utils');
const theme = require('../theme');
const run = async ({cwd, packages, version}, versionsMap) => {
const nodeModulesPath = join(cwd, 'build/node_modules');
// Cache all package JSONs for easy lookup below.
const sourcePackageJSONs = new Map();
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const sourcePackageJSON = await readJson(
join(cwd, 'packages', packageName, 'package.json')
);
sourcePackageJSONs.set(packageName, sourcePackageJSON);
}
const updateDependencies = async (targetPackageJSON, key) => {
const targetDependencies = targetPackageJSON[key];
if (targetDependencies) {
const sourceDependencies = sourcePackageJSONs.get(targetPackageJSON.name)[
key
];
for (let i = 0; i < packages.length; i++) {
const dependencyName = packages[i];
const targetDependency = targetDependencies[dependencyName];
if (targetDependency) {
// For example, say we're updating react-dom's dependency on scheduler.
// We compare source packages to determine what the new scheduler dependency constraint should be.
// To do this, we look at both the local version of the scheduler (e.g. 0.11.0),
// and the dependency constraint in the local version of react-dom (e.g. scheduler@^0.11.0).
const sourceDependencyVersion = sourcePackageJSONs.get(dependencyName)
.version;
const sourceDependencyConstraint = sourceDependencies[dependencyName];
// If the source dependency's version and the constraint match,
// we will need to update the constraint to point at the dependency's new release version,
// (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).
if (
sourceDependencyVersion ===
sourceDependencyConstraint.replace(/^[\^\~]/, '')
) {
targetDependencies[
dependencyName
] = sourceDependencyConstraint.replace(
sourceDependencyVersion,
versionsMap.get(dependencyName)
);
} else {
targetDependencies[dependencyName] = sourceDependencyConstraint;
}
}
}
}
};
// 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).
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
const packageJSON = await readJson(packageJSONPath);
packageJSON.version = versionsMap.get(packageName);
await updateDependencies(packageJSON, 'dependencies');
await updateDependencies(packageJSON, 'peerDependencies');
await writeJson(packageJSONPath, packageJSON, {spaces: 2});
}
clear();
// Print the map of versions and their dependencies for confirmation.
const printDependencies = (maybeDependency, label) => {
if (maybeDependency) {
for (let dependencyName in maybeDependency) {
if (packages.includes(dependencyName)) {
console.log(
theme`• {package ${dependencyName}} {version ${
maybeDependency[dependencyName]
}} {dimmed ${label}}`
);
}
}
}
};
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
const packageJSON = await readJson(packageJSONPath);
console.log(
theme`\n{package ${packageName}} {version ${versionsMap.get(
packageName
)}}`
);
printDependencies(packageJSON.dependencies, 'dependency');
printDependencies(packageJSON.peerDependencies, 'peer');
}
await confirm('Do the versions above look correct?');
clear();
// 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.
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.}`
);
process.exit(1);
}
// We print the diff to the console for review,
// but it can be large so let's also write it to disk.
const diffPath = join(cwd, 'build', 'temp.diff');
let diff = '';
let numFilesModified = 0;
// Find-and-replace hard coded version (in built JS) for renderers.
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packagePath = join(nodeModulesPath, packageName);
let files = await execRead(
`find ${packagePath} -name '*.js' -exec echo {} \\;`,
{cwd}
);
files = files.split('\n');
files.forEach(path => {
const newStableVersion = versionsMap.get(packageName);
const beforeContents = readFileSync(path, 'utf8', {cwd});
let afterContents = beforeContents;
// Replace all canary version numbers (e.g. header @license).
while (afterContents.indexOf(version) >= 0) {
afterContents = afterContents.replace(version, newStableVersion);
}
// Replace inline renderer version numbers (e.g. shared/ReactVersion).
while (afterContents.indexOf(reactVersion) >= 0) {
afterContents = afterContents.replace(reactVersion, newStableVersion);
}
if (beforeContents !== afterContents) {
numFilesModified++;
diff += printDiff(path, beforeContents, afterContents);
writeFileSync(path, afterContents, {cwd});
}
});
}
writeFileSync(diffPath, diff, {cwd});
console.log(theme.header(`\n${numFilesModified} files have been updated.`));
console.log(
theme`A full diff is available at {path ${relative(cwd, diffPath)}}.`
);
await confirm('Do the changes above look correct?');
};
// Run this directly because logPromise would interfere with printing package dependencies.
module.exports = run;

View File

@ -0,0 +1,36 @@
#!/usr/bin/env node
'use strict';
const {join} = require('path');
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 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');
const updateStableVersionNumbers = require('./prepare-stable-commands/update-stable-version-numbers');
const run = async () => {
try {
const params = parseParams();
params.cwd = join(__dirname, '..', '..');
params.packages = await getPublicPackages();
// Map of package name to upcoming stable version.
// This Map is initially populated with guesses based on local versions.
// The developer running the release later confirms or overrides each version.
const versionsMap = new Map();
await checkOutPackages(params);
await guessStableVersionNumbers(params, versionsMap);
await confirmStableVersionNumbers(params, versionsMap);
await updateStableVersionNumbers(params, versionsMap);
await printPrereleaseSummary(params);
} catch (error) {
handleError(error);
}
};
run();

View File

@ -1,35 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {existsSync} = require('fs');
const {readJson} = require('fs-extra');
const {join} = require('path');
module.exports = async ({cwd, version, local}) => {
if (local) {
return;
}
const packagePath = join(
cwd,
'build',
'node_modules',
'react',
'package.json'
);
if (!existsSync(packagePath)) {
throw Error('No build found');
}
const packageJson = await readJson(packagePath);
if (packageJson.version !== version) {
throw Error(
chalk`Expected version {bold.white ${version}} but found {bold.white ${
packageJson.version
}}`
);
}
};

View File

@ -2,10 +2,10 @@
'use strict';
const chalk = require('chalk');
const {execRead, logPromise} = require('../utils');
const theme = require('../theme');
module.exports = async ({packages}) => {
const run = async ({cwd, packages, version}) => {
const currentUser = await execRead('npm whoami');
const failedProjects = [];
@ -22,19 +22,23 @@ module.exports = async ({packages}) => {
await logPromise(
Promise.all(packages.map(checkProject)),
`Checking ${chalk.yellow.bold(currentUser)}'s NPM permissions`
theme`Checking NPM permissions for {underline ${currentUser}}.`
);
if (failedProjects.length) {
throw Error(
chalk`
Insufficient NPM permissions
{white NPM user {yellow.bold ${currentUser}} is not an owner for:}
{red ${failedProjects.join(', ')}}
{white Please contact a React team member to be added to the above project(s).}
console.error(
theme`
{error Insufficient NPM permissions}
\nNPM user {underline ${currentUser}} is not an owner for: ${failedProjects
.map(name => theme.package(name))
.join(', ')}
\nPlease contact a React team member to be added to the above project(s).
`
.replace(/\n +/g, '\n')
.trim()
);
process.exit(1);
}
};
module.exports = run;

View File

@ -1,24 +0,0 @@
#!/usr/bin/env node
'use strict';
const {exec} = require('child-process-promise');
const {execRead, logPromise} = require('../utils');
const update = async ({cwd, dry, version}) => {
const modifiedFiles = await execRead('git ls-files -m', {cwd});
if (!dry && modifiedFiles.includes('CHANGELOG.md')) {
await exec('git add CHANGELOG.md', {cwd});
await exec(
`git commit -am "Updating CHANGELOG.md for ${version} release"`,
{
cwd,
}
);
}
};
module.exports = async params => {
return logPromise(update(params), 'Committing CHANGELOG updates');
};

View File

@ -0,0 +1,46 @@
#!/usr/bin/env node
'use strict';
const clear = require('clear');
const {readJson} = require('fs-extra');
const {join} = require('path');
const {confirm} = require('../utils');
const theme = require('../theme');
const run = async ({cwd, packages, tags}) => {
clear();
if (tags.length === 1) {
console.log(
theme`{spinnerSuccess ✓} You are about the publish the following packages under the tag {tag ${tags}}`
);
} else {
console.log(
theme`{spinnerSuccess ✓} You are about the publish the following packages under the tags {tag ${tags.join(
', '
)}}`
);
}
// Cache all package JSONs for easy lookup below.
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packageJSONPath = join(
cwd,
'build/node_modules',
packageName,
'package.json'
);
const packageJSON = await readJson(packageJSONPath);
console.log(
theme`• {package ${packageName}} {version ${packageJSON.version}}`
);
}
await confirm('Do you want to proceed?');
};
// Run this directly because it's fast,
// and logPromise would interfere with console prompting.
module.exports = run;

View File

@ -0,0 +1,51 @@
#!/usr/bin/env node
'use strict';
const http = require('request-promise-json');
const {exec} = require('child-process-promise');
const {readJsonSync} = require('fs-extra');
const {logPromise} = require('../utils');
const theme = require('../theme');
const run = async ({cwd, tags}) => {
if (!tags.includes('latest')) {
// Don't update error-codes for alphas.
return;
}
// All packages are built from a single source revision,
// so it is safe to read build info from any one of them.
const {buildNumber, environment} = readJsonSync(
`${cwd}/build/node_modules/react/build-info.json`
);
// If this release was created on Circle CI, grab the updated error codes from there.
// Else the user will have to manually regenerate them.
if (environment === 'ci') {
// https://circleci.com/docs/2.0/artifacts/#downloading-all-artifacts-for-a-build-on-circleci
// eslint-disable-next-line max-len
const metadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildNumber}/artifacts?circle-token=${
process.env.CIRCLE_CI_API_TOKEN
}`;
const metadata = await http.get(metadataURL, true);
// Each container stores an "error-codes" artifact, unfortunately.
// We want to use the one that also ran `yarn build` since it may have modifications.
const {node_index} = metadata.find(
entry => entry.path === 'home/circleci/project/node_modules.tgz'
);
const {url} = metadata.find(
entry =>
entry.node_index === node_index &&
entry.path === 'home/circleci/project/scripts/error-codes/codes.json'
);
// Download and stage changers
await exec(`curl ${url} --output ./scripts/error-codes/codes.json`, {cwd});
}
};
module.exports = async params => {
return logPromise(run(params), theme`Retrieving error codes`);
};

View File

@ -1,15 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const logUpdate = require('log-update');
const prompt = require('prompt-promise');
module.exports = async params => {
logUpdate(chalk`{green ✓} Npm two-factor auth code {gray (or blank)}: `);
const otp = await prompt('');
prompt.done();
logUpdate.clear();
return otp.trim() || null;
};

View File

@ -0,0 +1,55 @@
#!/usr/bin/env node
'use strict';
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const paramDefinitions = [
{
name: 'dry',
type: Boolean,
description: 'Dry run command without actually publishing to NPM.',
defaultValue: false,
},
{
name: 'tags',
type: String,
multiple: true,
description: 'NPM tags to point to the new release.',
},
];
module.exports = () => {
const params = commandLineArgs(paramDefinitions);
if (!params.tags || params.tags.length === 0) {
const usage = commandLineUsage([
{
content:
'Publishes the current contents of "build/node_modules" to NPM.',
},
{
header: 'Options',
optionList: paramDefinitions,
},
{
header: 'Examples',
content: [
{
desc: 'Dry run test:',
example: '$ scripts/release/publish.js --dry --tags next',
},
{
desc: 'Publish a new stable:',
example: '$ scripts/release/publish.js --tags next latest',
},
],
},
]);
console.log(usage);
process.exit(1);
}
return params;
};

View File

@ -1,57 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const figlet = require('figlet');
const {paramDefinitions} = require('../config');
module.exports = () => {
const params = commandLineArgs(paramDefinitions);
if (!params.version) {
const usage = commandLineUsage([
{
content: chalk
.hex('#61dafb')
.bold(figlet.textSync('react', {font: 'Graffiti'})),
raw: true,
},
{
content: 'Automated release publishing script.',
},
{
header: 'Options',
optionList: paramDefinitions,
},
{
header: 'Examples',
content: [
{
desc: '1. A concise example.',
example: '$ ./publish.js [bold]{-v} [underline]{16.0.0}',
},
{
desc: '2. Dry run publish a release candidate.',
example:
'$ ./publish.js [bold]{--dry} [bold]{-v} [underline]{16.0.0-rc.0}',
},
{
desc: '3. Release from another checkout.',
example:
'$ ./publish.js [bold]{--version}=[underline]{16.0.0} [bold]{--path}=/path/to/react/repo',
},
],
},
]);
console.log(usage);
process.exit(1);
}
return {
...params,
cwd: params.path, // For script convenience
};
};

View File

@ -0,0 +1,84 @@
#!/usr/bin/env node
'use strict';
const clear = require('clear');
const {readJsonSync} = require('fs-extra');
const theme = require('../theme');
const run = async ({cwd, packages, tags}) => {
// All packages are built from a single source revision,
// so it is safe to read the commit number from any one of them.
const {commit, environment} = readJsonSync(
`${cwd}/build/node_modules/react/build-info.json`
);
// Tags are named after the react version.
const {version} = readJsonSync(
`${cwd}/build/node_modules/react/package.json`
);
clear();
console.log(
theme.caution`The release has been published but you're not done yet!`
);
if (tags.includes('latest')) {
console.log();
console.log(
theme.header`Please review and commit all local, staged changes.`
);
console.log();
console.log('Version numbers have been updated in the following files:');
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
console.log(theme.path`• packages/%s/package.json`, packageName);
}
console.log(theme.path`• packages/shared/ReactVersion.js`);
console.log();
if (environment === 'ci') {
console.log('Auto-generated error codes have been updated as well:');
console.log(theme.path`• scripts/error-codes/codes.json`);
} else {
console.log(
theme`{caution The release that was just published was created locally.} ` +
theme`Because of this, you will need to update the generated ` +
theme`{path scripts/error-codes/codes.json} file manually:`
);
console.log(theme` {command git checkout} {version ${commit}}`);
console.log(theme` {command yarn build -- --extract-errors}`);
}
}
console.log();
console.log(
theme`{header Don't forget to update and commit the }{path CHANGELOG}`
);
// Prompt the release engineer to tag the commit and update the CHANGELOG.
// (The script could automatically do this, but this seems safer.)
console.log();
console.log(
theme.header`Tag the source for this release in Git with the following command:`
);
console.log(
theme` {command git tag -a v}{version %s} {command -m "v%s"} {version %s}`,
version,
version,
commit
);
console.log(theme.command` git push origin --tags`);
console.log();
console.log(theme.header`Lastly, please fill in the release on GitHub:`);
console.log(
theme.link`https://github.com/facebook/react/releases/tag/v%s`,
version
);
console.log();
};
module.exports = run;

View File

@ -1,78 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const semver = require('semver');
const {getUnexecutedCommands} = require('../utils');
const printSteps = steps => {
return steps
.filter(Boolean) // Remove no-op steps
.map((step, index) => `${index + 1}. ${step}`)
.join('\n');
};
const printSections = sections => {
return sections
.map((section, index) => {
const [title, ...steps] = section;
return chalk`
{bold.underline Step ${index + 1}: ${title}}
${printSteps(steps)}
`.replace(/\n +/g, '\n');
})
.join('');
};
module.exports = ({dry, version}) => {
const isPrerelease = semver.prerelease(version);
const sections = [];
// Certain follow-up steps are for stable releases only.
if (!isPrerelease) {
sections.push([
'Create GitHub release',
chalk`Open new release page: {blue.bold https://github.com/facebook/react/releases/new}`,
chalk`Choose {bold ${version}} from the dropdown menu`,
chalk`Paste the new release notes from {yellow.bold CHANGELOG.md}`,
chalk`Attach all files in {yellow.bold build/dist/*.js} except {yellow.bold react-art.*} to the release.`,
chalk`Press {bold "Publish release"}!`,
]);
sections.push([
'Update the version on reactjs.org',
chalk`Git clone (or update) {blue.bold https://github.com/reactjs/reactjs.org}`,
chalk`Open the {bold.yellow src/site-constants.js} file`,
chalk`Update the {bold version} value to {bold ${version}}`,
chalk`Open a Pull Request to {bold master}`,
]);
}
sections.push([
'Test the new release',
chalk`Install CRA: {bold npm i -g create-react-app}`,
chalk`Create a test application: {bold create-react-app myapp && cd myapp}`,
isPrerelease
? chalk`Install the pre-release versions: {bold yarn add react@next react-dom@next}`
: null,
chalk`Run the app: {bold yarn start}`,
]);
sections.push([
'Notify the DOM team',
chalk`Notify DOM team members: {bold @nhunzaker @jquense @aweary}`,
]);
console.log(
chalk`
{green.bold Publish successful!}
${getUnexecutedCommands()}
Next there are a couple of manual steps:
${printSections(sections)}
`.replace(/\n +/g, '\n')
);
};

View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
'use strict';
const prompt = require('prompt-promise');
const theme = require('../theme');
const run = async () => {
while (true) {
const otp = await prompt('NPM 2-factor auth code: ');
prompt.done();
if (otp) {
return otp;
} else {
console.log();
console.log(theme.error`Two-factor auth is required to publish.`);
// (Ask again.)
}
}
};
module.exports = run;

View File

@ -2,94 +2,61 @@
'use strict';
const chalk = require('chalk');
const {readJson} = require('fs-extra');
const {exec} = require('child-process-promise');
const {readJsonSync} = require('fs-extra');
const {join} = require('path');
const semver = require('semver');
const {execRead, execUnlessDry, logPromise} = require('../utils');
const {confirm, execRead} = require('../utils');
const theme = require('../theme');
const push = async ({cwd, dry, otp, packages, version, tag}) => {
const errors = [];
const isPrerelease = semver.prerelease(version);
const run = async ({cwd, dry, packages, tags}, otp) => {
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packagePath = join(cwd, 'build/node_modules', packageName);
const {version} = readJsonSync(join(packagePath, 'package.json'));
let resolvedTag = tag;
if (tag === undefined) {
// No tag was provided. Default to `latest` for stable releases and `next`
// for prereleases
resolvedTag = isPrerelease ? 'next' : 'latest';
} else if (tag === 'latest' && isPrerelease) {
throw new Error('The tag `latest` can only be used for stable versions.');
}
// Pass two factor auth code if provided:
// https://docs.npmjs.com/getting-started/using-two-factor-authentication
const twoFactorAuth = otp != null ? `--otp ${otp}` : '';
const publishProject = async project => {
try {
const path = join(cwd, 'build', 'node_modules', project);
await execUnlessDry(`npm publish --tag ${resolvedTag} ${twoFactorAuth}`, {
cwd: path,
dry,
});
const packagePath = join(
cwd,
'build',
'node_modules',
project,
'package.json'
// Check if this package version has already been published.
// If so we might be resuming from a previous run.
// We could infer this by comparing the build-info.json,
// But for now the easiest way is just to ask if this is expected.
const info = await execRead(`npm view ${packageName}@${version}`);
if (info) {
console.log(
theme`{package ${packageName}} {version ${version}} has already been published.`
);
await confirm('Is this expected?');
} else {
console.log(
theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`
);
const packageJSON = await readJson(packagePath);
const packageVersion = packageJSON.version;
// Publish the package and tag it.
if (!dry) {
// Wait a couple of seconds before querying NPM for status;
// Anecdotally, querying too soon can result in a false negative.
await new Promise(resolve => setTimeout(resolve, 5000));
const status = JSON.parse(
await execRead(`npm info ${project} dist-tags --json`)
);
const remoteVersion = status[resolvedTag];
// Compare remote version to package.json version,
// To better handle the case of pre-release versions.
if (remoteVersion !== packageVersion) {
throw Error(
chalk`Published version {yellow.bold ${packageVersion}} for ` +
chalk`{bold ${project}} but NPM shows {yellow.bold ${remoteVersion}}`
);
}
// If we've just published a stable release,
// Update the @next tag to also point to it (so @next doesn't lag behind).
// Skip this step if we have a manually specified tag.
// This is an escape hatch for us to interleave alpha and stable releases.
if (tag === undefined && !isPrerelease) {
await execUnlessDry(
`npm dist-tag add ${project}@${packageVersion} next ${twoFactorAuth}`,
{cwd: path, dry}
);
}
await exec(`npm publish --tag=${tags[0]} --otp=${otp}`, {
cwd: packagePath,
});
}
console.log(theme.command(` cd ${packagePath}`));
console.log(theme.command(` npm publish --tag=${tags[0]} --otp=${otp}`));
for (let j = 1; j < tags.length; j++) {
if (!dry) {
await exec(
`npm dist-tag add ${packageName}@${version} ${
tags[j]
} --otp=${otp}`,
{cwd: packagePath}
);
}
console.log(
theme.command(
` npm dist-tag add ${packageName}@${version} ${
tags[j]
} --otp=${otp}`
)
);
}
} catch (error) {
errors.push(error.stack);
}
};
await Promise.all(packages.map(publishProject));
if (errors.length > 0) {
throw Error(
chalk`
Failure publishing to NPM
{white ${errors.join('\n\n')}}`
);
}
};
module.exports = async params => {
return logPromise(push(params), 'Publishing packages to NPM');
};
module.exports = run;

View File

@ -1,17 +0,0 @@
#!/usr/bin/env node
'use strict';
const {execUnlessDry, logPromise} = require('../utils');
const push = async ({cwd, dry}) => {
await execUnlessDry('git push', {cwd, dry});
await execUnlessDry('git push --tags', {cwd, dry});
};
module.exports = async params => {
if (params.local) {
return;
}
return logPromise(push(params), 'Pushing to git remote');
};

View File

@ -0,0 +1,49 @@
#!/usr/bin/env node
'use strict';
const {readFileSync, writeFileSync} = require('fs');
const {readJson, writeJson} = require('fs-extra');
const {join} = require('path');
const run = async ({cwd, packages, tags}) => {
if (!tags.includes('latest')) {
// Don't update version numbers for alphas.
return;
}
const nodeModulesPath = join(cwd, 'build/node_modules');
const packagesPath = join(cwd, 'packages');
// Update package versions and dependencies (in source) to mirror what was published to NPM.
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const publishedPackageJSON = await readJson(
join(nodeModulesPath, packageName, 'package.json')
);
const sourcePackageJSONPath = join(
packagesPath,
packageName,
'package.json'
);
const sourcePackageJSON = await readJson(sourcePackageJSONPath);
sourcePackageJSON.version = publishedPackageJSON.version;
sourcePackageJSON.dependencies = publishedPackageJSON.dependencies;
sourcePackageJSON.peerDependencies = publishedPackageJSON.peerDependencies;
await writeJson(sourcePackageJSONPath, sourcePackageJSON, {spaces: 2});
}
// Update the shared React version source file.
const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
const {version} = await readJson(
join(nodeModulesPath, 'react', 'package.json')
);
const sourceReactVersion = readFileSync(
sourceReactVersionPath,
'utf8'
).replace(/module\.exports = '[^']+';/, `module.exports = '${version}';`);
writeFileSync(sourceReactVersionPath, sourceReactVersion);
};
module.exports = run;

View File

@ -0,0 +1,29 @@
#!/usr/bin/env node
'use strict';
const {readJson} = require('fs-extra');
const {join} = require('path');
const theme = require('../theme');
const run = async ({cwd, packages, tags}) => {
// Prevent a canary release from ever being published as @latest
const packageJSONPath = join(
cwd,
'build',
'node_modules',
'react',
'package.json'
);
const {version} = await readJson(packageJSONPath);
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}`
);
process.exit(1);
}
}
};
module.exports = run;

View File

@ -2,43 +2,35 @@
'use strict';
const chalk = require('chalk');
const logUpdate = require('log-update');
const {getPublicPackages} = require('./utils');
const {join} = require('path');
const {getPublicPackages, handleError} = require('./utils');
const checkBuildStatus = require('./publish-commands/check-build-status');
const commitChangelog = require('./publish-commands/commit-changelog');
const getNpmTwoFactorAuth = require('./publish-commands/get-npm-two-factor-auth');
const parsePublishParams = require('./publish-commands/parse-publish-params');
const printPostPublishSummary = require('./publish-commands/print-post-publish-summary');
const pushGitRemote = require('./publish-commands/push-git-remote');
const publishToNpm = require('./publish-commands/publish-to-npm');
const checkNPMPermissions = require('./publish-commands/check-npm-permissions');
const confirmVersionAndTags = require('./publish-commands/confirm-version-and-tags');
const downloadErrorCodesFromCI = require('./publish-commands/download-error-codes-from-ci');
const parseParams = require('./publish-commands/parse-params');
const printFollowUpInstructions = require('./publish-commands/print-follow-up-instructions');
const promptForOTP = require('./publish-commands/prompt-for-otp');
const publishToNPM = require('./publish-commands/publish-to-npm');
const updateStableVersionNumbers = require('./publish-commands/update-stable-version-numbers');
const validateTags = require('./publish-commands/validate-tags');
// Follows the steps outlined in github.com/facebook/react/issues/10620
const run = async () => {
const params = parsePublishParams();
params.packages = getPublicPackages();
try {
await checkBuildStatus(params);
await commitChangelog(params);
await pushGitRemote(params);
params.otp = await getNpmTwoFactorAuth(params);
await publishToNpm(params);
await printPostPublishSummary(params);
const params = parseParams();
params.cwd = join(__dirname, '..', '..');
params.packages = await getPublicPackages();
await validateTags(params);
await confirmVersionAndTags(params);
await checkNPMPermissions(params);
const otp = await promptForOTP(params);
await publishToNPM(params, otp);
await downloadErrorCodesFromCI(params);
await updateStableVersionNumbers(params);
await printFollowUpInstructions(params);
} catch (error) {
logUpdate.clear();
const message = error.message.trim().replace(/\n +/g, '\n');
const stack = error.stack.replace(error.message, '');
console.log(
`${chalk.bgRed.white(' ERROR ')} ${chalk.red(message)}\n\n${chalk.gray(
stack
)}`
);
process.exit(1);
handleError(error);
}
};

View File

@ -0,0 +1,38 @@
#!/usr/bin/env node
'use strict';
const clear = require('clear');
const {join, relative} = require('path');
const theme = require('../theme');
module.exports = ({cwd}) => {
const publishPath = relative(
process.env.PWD,
join(__dirname, '../publish.js')
);
clear();
console.log(
theme`
{caution A release candidate has been prepared but you're not done yet!}
You can review the contents of this release in {path ./build/node_modules/}
{header Before publishing, please smoke test the packages!}
1. Open {path ./fixtures/packaging/babel-standalone/dev.html} in the browser.
2. It should say {quote "Hello world!"}
3. Next go to {path ./fixtures/packaging} and run {command node build-all.js}
4. Go to the repo root and {command npx pushstate-server . 9000}
5. Open {link http://localhost:9000/fixtures/packaging}
6. Verify every iframe shows {quote "Hello world!"}
After completing the above steps, you can publish this release by running:
{path ${publishPath}}
`
.replace(/\n +/g, '\n')
.trim()
);
};

35
scripts/release/theme.js Normal file
View File

@ -0,0 +1,35 @@
'use strict';
const chalk = require('chalk');
const colors = {
blue: '#0091ea',
gray: '#78909c',
green: '#00c853',
red: '#d50000',
yellow: '#ffd600',
};
const theme = chalk.constructor();
theme.package = theme.hex(colors.green);
theme.version = theme.hex(colors.yellow);
theme.tag = theme.hex(colors.yellow);
theme.build = theme.hex(colors.yellow);
theme.error = theme.hex(colors.red).bold;
theme.dimmed = theme.hex(colors.gray);
theme.caution = theme.hex(colors.red).bold;
theme.link = theme.hex(colors.blue).underline.italic;
theme.header = theme.hex(colors.green).bold;
theme.path = theme.hex(colors.gray).italic;
theme.command = theme.hex(colors.gray);
theme.quote = theme.italic;
theme.diffHeader = theme.hex(colors.gray);
theme.diffAdded = theme.hex(colors.green);
theme.diffRemoved = theme.hex(colors.red);
theme.spinnerInProgress = theme.hex(colors.yellow);
theme.spinnerError = theme.hex(colors.red);
theme.spinnerSuccess = theme.hex(colors.green);
module.exports = theme;

View File

@ -1,11 +1,24 @@
'use strict';
const chalk = require('chalk');
const {dots} = require('cli-spinners');
const {exec} = require('child-process-promise');
const {readdirSync, readFileSync, statSync} = require('fs');
const {createPatch} = require('diff');
const {hashElement} = require('folder-hash');
const {readdirSync, readFileSync, statSync, writeFileSync} = require('fs');
const {readJson, writeJson} = require('fs-extra');
const logUpdate = require('log-update');
const {join} = require('path');
const prompt = require('prompt-promise');
const theme = require('./theme');
const confirm = async message => {
const confirmation = await prompt(theme`\n{caution ${message}} (y/N) `);
prompt.done();
if (confirmation !== 'y' && confirmation !== 'Y') {
console.log(theme`\n{caution Release cancelled.}`);
process.exit(0);
}
};
const execRead = async (command, options) => {
const {stdout} = await exec(command, options);
@ -13,31 +26,37 @@ const execRead = async (command, options) => {
return stdout.trim();
};
const unexecutedCommands = [];
const getBuildInfo = async () => {
const cwd = join(__dirname, '..', '..');
const execUnlessDry = async (command, {cwd, dry}) => {
if (dry) {
unexecutedCommands.push(`${command} # {cwd: ${cwd}}`);
} else {
await exec(command, {cwd});
}
const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
cwd,
});
const commit = await execRead('git show -s --format=%h', {cwd});
const checksum = await getChecksumForCurrentRevision(cwd);
const version = `0.0.0-${commit}`;
// Only available for Circle CI builds.
// https://circleci.com/docs/2.0/env-vars/
const buildNumber = process.env.CIRCLE_BUILD_NUM;
// React version is stored explicitly, separately for DevTools support.
// See updateVersionsForCanary() below for more info.
const packageJSON = await readJson(
join(cwd, 'packages', 'react', 'package.json')
);
const reactVersion = `${packageJSON.version}-canary-${commit}`;
return {branch, buildNumber, checksum, commit, reactVersion, version};
};
const getPackages = () => {
const packagesRoot = join(__dirname, '..', '..', 'packages');
return readdirSync(packagesRoot).filter(dir => {
const packagePath = join(packagesRoot, dir, 'package.json');
if (dir.charAt(0) !== '.' && statSync(packagePath).isFile()) {
const packageJSON = JSON.parse(readFileSync(packagePath));
// Skip packages like "shared" and "events" that shouldn't be updated.
return packageJSON.version !== '0.0.0';
}
return false;
const getChecksumForCurrentRevision = async cwd => {
const packagesDir = join(cwd, 'packages');
const hashedPackages = await hashElement(packagesDir, {
encoding: 'hex',
files: {exclude: ['.DS_Store']},
});
return hashedPackages.hash.slice(0, 7);
};
const getPublicPackages = () => {
@ -56,15 +75,15 @@ const getPublicPackages = () => {
});
};
const getUnexecutedCommands = () => {
if (unexecutedCommands.length > 0) {
return chalk`
The following commands were not executed because of the {bold --dry} flag:
{gray ${unexecutedCommands.join('\n')}}
`;
} else {
return '';
}
const handleError = error => {
logUpdate.clear();
const message = error.message.trim().replace(/\n +/g, '\n');
const stack = error.stack.replace(error.message, '');
console.log(theme`{error ${message}}\n\n{path ${stack}}`);
process.exit(1);
};
const logPromise = async (promise, text, isLongRunningTask = false) => {
@ -79,7 +98,9 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
const id = setInterval(() => {
index = ++index % frames.length;
logUpdate(
`${chalk.yellow(frames[index])} ${text} ${chalk.gray(inProgressMessage)}`
theme`{spinnerInProgress ${
frames[index]
}} ${text} {dimmed ${inProgressMessage}}`
);
}, interval);
@ -88,7 +109,7 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
clearInterval(id);
logUpdate(`${chalk.green('✓')} ${text}`);
logUpdate(theme`{spinnerSuccess ✓} ${text}`);
logUpdate.done();
return returnValue;
@ -99,26 +120,103 @@ const logPromise = async (promise, text, isLongRunningTask = false) => {
}
};
const runYarnTask = async (cwd, task, errorMessage) => {
try {
await exec(`yarn ${task}`, {cwd});
} catch (error) {
throw Error(
chalk`
${errorMessage}
const printDiff = (path, beforeContents, afterContents) => {
const patch = createPatch(path, beforeContents, afterContents);
const coloredLines = patch
.split('\n')
.slice(2) // Trim index file
.map((line, index) => {
if (index <= 1) {
return theme.diffHeader(line);
}
switch (line[0]) {
case '+':
return theme.diffAdded(line);
case '-':
return theme.diffRemoved(line);
case ' ':
return line;
case '@':
return null;
case '\\':
return null;
}
})
.filter(line => line);
console.log(coloredLines.join('\n'));
return patch;
};
{white ${error.stdout}}
`
);
// 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).
// 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) => {
const packages = getPublicPackages(join(cwd, 'packages'));
const packagesDir = join(cwd, 'packages');
// Update the shared React version source file.
// This is bundled into built renderers.
// The promote script will replace this with a final version later.
const sourceReactVersionPath = join(cwd, 'packages/shared/ReactVersion.js');
const sourceReactVersion = readFileSync(
sourceReactVersionPath,
'utf8'
).replace(
/module\.exports = '[^']+';/,
`module.exports = '${reactVersion}';`
);
writeFileSync(sourceReactVersionPath, sourceReactVersion);
// Update the root package.json.
// This is required to pass a later version check script.
{
const packageJSONPath = join(cwd, 'package.json');
const packageJSON = await readJson(packageJSONPath);
packageJSON.version = version;
await writeJson(packageJSONPath, packageJSON, {spaces: 2});
}
for (let i = 0; i < packages.length; i++) {
const packageName = packages[i];
const packagePath = join(packagesDir, packageName);
// Update version numbers in package JSONs
const packageJSONPath = join(packagePath, 'package.json');
const packageJSON = await readJson(packageJSONPath);
packageJSON.version = version;
// Also update inter-package dependencies.
// Canary 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++) {
const dependencyName = packages[j];
if (dependencies && dependencies[dependencyName]) {
dependencies[dependencyName] = version;
}
if (peerDependencies && peerDependencies[dependencyName]) {
peerDependencies[dependencyName] = version;
}
}
await writeJson(packageJSONPath, packageJSON, {spaces: 2});
}
};
module.exports = {
confirm,
execRead,
execUnlessDry,
getPackages,
getBuildInfo,
getChecksumForCurrentRevision,
getPublicPackages,
getUnexecutedCommands,
handleError,
logPromise,
runYarnTask,
printDiff,
theme,
updateVersionsForCanary,
};

View File

@ -63,6 +63,11 @@ aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bcrypt-pbkdf@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
@ -81,6 +86,14 @@ boom@5.x.x:
dependencies:
hoek "4.x.x"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@ -101,6 +114,11 @@ child-process-promise@^2.2.1:
node-version "^1.0.0"
promise-polyfill "^6.0.1"
clear@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a"
integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==
cli-cursor@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@ -148,6 +166,11 @@ command-line-usage@^4.0.1:
table-layout "^0.4.1"
typical "^2.6.1"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -171,6 +194,13 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
deep-extend@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803"
@ -179,6 +209,11 @@ delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@ -201,10 +236,6 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
figlet@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410"
find-replace@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0"
@ -212,6 +243,15 @@ find-replace@^1.0.3:
array-back "^1.0.4"
test-value "^2.1.0"
folder-hash@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/folder-hash/-/folder-hash-2.1.2.tgz#7109f9cd0cbca271936d1b5544b156d6571e6cfd"
integrity sha512-PmMwEZyNN96EMshf7sek4OIB7ADNsHOJ7VIw7pO0PBI0BNfEsi7U8U56TBjjqqwQ0WuBv8se0HEfmbw5b/Rk+w==
dependencies:
debug "^3.1.0"
graceful-fs "~4.1.11"
minimatch "~3.0.4"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@ -242,6 +282,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
graceful-fs@~4.1.11:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@ -372,6 +417,18 @@ mimic-fn@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
minimatch@~3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
native-or-another@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/native-or-another/-/native-or-another-2.0.0.tgz#17a567f92beea9cd71acff96a7681a735eca3bff"