mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
DevTools release script updates: (#22170)
This commit is contained in:
3
scripts/devtools/.gitignore
vendored
3
scripts/devtools/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.progress-estimator
|
||||
.build-metadata
|
||||
.progress-estimator
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Releasing DevTools
|
||||
|
||||
To release DevTools, do the following steps (in order):
|
||||
1. [Prepare a release](#prepare-a-release)
|
||||
2. [Build and test a release](#build-and-test-a-release)
|
||||
3. [Publish a release](#publish-a-release)
|
||||
|
||||
Each of the scripts can be run with a `--dry` flag to test without committing or publishing any changes.
|
||||
|
||||
### Prepare a release
|
||||
To increment version numbers and update the [CHANGELOG](https://github.com/facebook/react/blob/main/packages/react-devtools/CHANGELOG.md), run the `prepare-release` script:
|
||||
```sh
|
||||
./prepare-release.js
|
||||
```
|
||||
|
||||
You'll need to follow the instructions at the end of the script to push the committed changes to the main fork on GitHub.
|
||||
|
||||
### Build and test a release
|
||||
To build and test a release, run the `build-and-test` script:
|
||||
```sh
|
||||
./build-and-test.js
|
||||
```
|
||||
|
||||
### Publish a release
|
||||
To publish a release to NPM, run the `publish-release` script:
|
||||
```sh
|
||||
./publish-release.js
|
||||
```
|
||||
|
||||
You'll need to follow the instructions at the end of the script to upload the extension to Chrome, Edge, and Firefox stores.
|
||||
221
scripts/devtools/build-and-test.js
Executable file
221
scripts/devtools/build-and-test.js
Executable file
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {exec} = require('child-process-promise');
|
||||
const inquirer = require('inquirer');
|
||||
const {homedir} = require('os');
|
||||
const {join, relative} = require('path');
|
||||
const {DRY_RUN, ROOT_PATH} = require('./configuration');
|
||||
const {
|
||||
clear,
|
||||
confirm,
|
||||
confirmContinue,
|
||||
execRead,
|
||||
logger,
|
||||
saveBuildMetadata,
|
||||
} = require('./utils');
|
||||
|
||||
// This is the primary control function for this script.
|
||||
async function main() {
|
||||
clear();
|
||||
|
||||
await confirm('Have you run the prepare-release script?', () => {
|
||||
const prepareReleaseScriptPath = join(__dirname, 'prepare-release.js');
|
||||
const pathToPrint = relative(process.cwd(), prepareReleaseScriptPath);
|
||||
|
||||
console.log('Begin by running the prepare-release script:');
|
||||
console.log(chalk.bold.green(' ' + pathToPrint));
|
||||
});
|
||||
|
||||
const archivePath = await archiveGitRevision();
|
||||
const buildID = await downloadLatestReactBuild();
|
||||
|
||||
await buildAndTestInlinePackage();
|
||||
await buildAndTestStandalonePackage();
|
||||
await buildAndTestExtensions();
|
||||
|
||||
saveBuildMetadata({archivePath, buildID});
|
||||
|
||||
printFinalInstructions();
|
||||
}
|
||||
|
||||
async function archiveGitRevision() {
|
||||
const desktopPath = join(homedir(), 'Desktop');
|
||||
const archivePath = join(desktopPath, 'DevTools.tgz');
|
||||
|
||||
console.log(`Creating git archive at ${chalk.dim(archivePath)}`);
|
||||
console.log('');
|
||||
|
||||
if (!DRY_RUN) {
|
||||
await exec(`git archive main | gzip > ${archivePath}`, {cwd: ROOT_PATH});
|
||||
}
|
||||
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
async function buildAndTestExtensions() {
|
||||
const extensionsPackagePath = join(
|
||||
ROOT_PATH,
|
||||
'packages',
|
||||
'react-devtools-extensions'
|
||||
);
|
||||
const buildExtensionsPromise = exec('yarn build', {
|
||||
cwd: extensionsPackagePath,
|
||||
});
|
||||
|
||||
await logger(
|
||||
buildExtensionsPromise,
|
||||
`Building browser extensions ${chalk.dim('(this may take a minute)')}`,
|
||||
{
|
||||
estimate: 60000,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log(`Extensions have been build for Chrome, Edge, and Firefox.`);
|
||||
console.log('');
|
||||
console.log('Smoke test each extension before continuing:');
|
||||
console.log(` ${chalk.bold.green('cd ' + extensionsPackagePath)}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Test Chrome extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:chrome')}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Test Edge extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:edge')}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Firefox Chrome extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:firefox')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function buildAndTestStandalonePackage() {
|
||||
const corePackagePath = join(ROOT_PATH, 'packages', 'react-devtools-core');
|
||||
const buildCorePromise = exec('yarn build', {cwd: corePackagePath});
|
||||
|
||||
await logger(
|
||||
buildCorePromise,
|
||||
`Building ${chalk.bold('react-devtools-core')} package.`,
|
||||
{
|
||||
estimate: 25000,
|
||||
}
|
||||
);
|
||||
|
||||
const standalonePackagePath = join(ROOT_PATH, 'packages', 'react-devtools');
|
||||
const safariFixturePath = join(
|
||||
ROOT_PATH,
|
||||
'fixtures',
|
||||
'devtools',
|
||||
'standalone',
|
||||
'index.html'
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`Test the ${chalk.bold('react-devtools-core')} target before continuing:`
|
||||
);
|
||||
console.log(` ${chalk.bold.green('cd ' + standalonePackagePath)}`);
|
||||
console.log(` ${chalk.bold.green('yarn start')}`);
|
||||
console.log('');
|
||||
console.log(
|
||||
'The following fixture can be useful for testing Safari integration:'
|
||||
);
|
||||
console.log(` ${chalk.dim(safariFixturePath)}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function buildAndTestInlinePackage() {
|
||||
const inlinePackagePath = join(
|
||||
ROOT_PATH,
|
||||
'packages',
|
||||
'react-devtools-inline'
|
||||
);
|
||||
const buildPromise = exec('yarn build', {cwd: inlinePackagePath});
|
||||
|
||||
await logger(
|
||||
buildPromise,
|
||||
`Building ${chalk.bold('react-devtools-inline')} package.`,
|
||||
{
|
||||
estimate: 10000,
|
||||
}
|
||||
);
|
||||
|
||||
const shellPackagePath = join(ROOT_PATH, 'packages', 'react-devtools-shell');
|
||||
|
||||
console.log('');
|
||||
console.log(`Built ${chalk.bold('react-devtools-inline')} target.`);
|
||||
console.log('');
|
||||
console.log('Test this build before continuing:');
|
||||
console.log(` ${chalk.bold.green('cd ' + shellPackagePath)}`);
|
||||
console.log(` ${chalk.bold.green('yarn start')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function downloadLatestReactBuild() {
|
||||
const releaseScriptPath = join(ROOT_PATH, 'scripts', 'release');
|
||||
const installPromise = exec('yarn install', {cwd: releaseScriptPath});
|
||||
|
||||
await logger(
|
||||
installPromise,
|
||||
`Installing release script dependencies. ${chalk.dim(
|
||||
'(this may take a minute if CI is still running)'
|
||||
)}`,
|
||||
{
|
||||
estimate: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('');
|
||||
|
||||
const {commit} = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'commit',
|
||||
message: 'Which React version (commit) should be used?',
|
||||
default: 'main',
|
||||
},
|
||||
]);
|
||||
console.log('');
|
||||
|
||||
const downloadScriptPath = join(
|
||||
releaseScriptPath,
|
||||
'download-experimental-build.js'
|
||||
);
|
||||
const downloadPromise = execRead(
|
||||
`"${downloadScriptPath}" --commit=${commit}`
|
||||
);
|
||||
|
||||
const output = await logger(
|
||||
downloadPromise,
|
||||
'Downloading React artifacts from CI.',
|
||||
{estimate: 15000}
|
||||
);
|
||||
|
||||
const match = output.match('--build=([0-9]+)');
|
||||
if (match.length === 0) {
|
||||
console.error(chalk.red(`No build ID found in "${output}"`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const buildID = match[1];
|
||||
|
||||
console.log('');
|
||||
console.log(`Downloaded artiacts for CI build ${chalk.bold(buildID)}.`);
|
||||
|
||||
return buildID;
|
||||
}
|
||||
|
||||
function printFinalInstructions() {
|
||||
const publishReleaseScriptPath = join(__dirname, 'publish-release.js');
|
||||
const pathToPrint = relative(process.cwd(), publishReleaseScriptPath);
|
||||
|
||||
console.log('');
|
||||
console.log('Continue by running the publish-release script:');
|
||||
console.log(chalk.bold.green(' ' + pathToPrint));
|
||||
}
|
||||
|
||||
main();
|
||||
46
scripts/devtools/configuration.js
Normal file
46
scripts/devtools/configuration.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const {join} = require('path');
|
||||
|
||||
const PACKAGE_PATHS = [
|
||||
'packages/react-devtools/package.json',
|
||||
'packages/react-devtools-core/package.json',
|
||||
'packages/react-devtools-inline/package.json',
|
||||
'packages/react-devtools-scheduling-profiler/package.json',
|
||||
];
|
||||
|
||||
const MANIFEST_PATHS = [
|
||||
'packages/react-devtools-extensions/chrome/manifest.json',
|
||||
'packages/react-devtools-extensions/edge/manifest.json',
|
||||
'packages/react-devtools-extensions/firefox/manifest.json',
|
||||
];
|
||||
|
||||
const NPM_PACKAGES = [
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-inline',
|
||||
];
|
||||
|
||||
const CHANGELOG_PATH = 'packages/react-devtools/CHANGELOG.md';
|
||||
|
||||
const PULL_REQUEST_BASE_URL = 'https://github.com/facebook/react/pull/';
|
||||
|
||||
const RELEASE_SCRIPT_TOKEN = '<!-- RELEASE_SCRIPT_TOKEN -->';
|
||||
|
||||
const ROOT_PATH = join(__dirname, '..', '..');
|
||||
|
||||
const DRY_RUN = process.argv.includes('--dry');
|
||||
|
||||
const BUILD_METADATA_TEMP_DIRECTORY = join(__dirname, '.build-metadata');
|
||||
|
||||
module.exports = {
|
||||
BUILD_METADATA_TEMP_DIRECTORY,
|
||||
CHANGELOG_PATH,
|
||||
DRY_RUN,
|
||||
MANIFEST_PATHS,
|
||||
NPM_PACKAGES,
|
||||
PACKAGE_PATHS,
|
||||
PULL_REQUEST_BASE_URL,
|
||||
RELEASE_SCRIPT_TOKEN,
|
||||
ROOT_PATH,
|
||||
};
|
||||
254
scripts/devtools/prepare-release.js
Executable file
254
scripts/devtools/prepare-release.js
Executable file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {exec} = require('child-process-promise');
|
||||
const {readFileSync, writeFileSync} = require('fs');
|
||||
const {readJsonSync, writeJsonSync} = require('fs-extra');
|
||||
const inquirer = require('inquirer');
|
||||
const {join, relative} = require('path');
|
||||
const semver = require('semver');
|
||||
const {
|
||||
CHANGELOG_PATH,
|
||||
DRY_RUN,
|
||||
MANIFEST_PATHS,
|
||||
PACKAGE_PATHS,
|
||||
PULL_REQUEST_BASE_URL,
|
||||
RELEASE_SCRIPT_TOKEN,
|
||||
ROOT_PATH,
|
||||
} = require('./configuration');
|
||||
const {
|
||||
checkNPMPermissions,
|
||||
clear,
|
||||
confirmContinue,
|
||||
execRead,
|
||||
} = require('./utils');
|
||||
|
||||
// This is the primary control function for this script.
|
||||
async function main() {
|
||||
clear();
|
||||
|
||||
await checkNPMPermissions();
|
||||
|
||||
const releaseType = await getReleaseType();
|
||||
|
||||
const path = join(ROOT_PATH, PACKAGE_PATHS[0]);
|
||||
const previousVersion = readJsonSync(path).version;
|
||||
const {major, minor, patch} = semver(previousVersion);
|
||||
const nextVersion =
|
||||
releaseType === 'minor'
|
||||
? `${major}.${minor + 1}.${patch}`
|
||||
: `${major}.${minor}.${patch + 1}`;
|
||||
|
||||
updatePackageVersions(previousVersion, nextVersion);
|
||||
updateManifestVersions(previousVersion, nextVersion);
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`Packages and manifests have been updated from version ${chalk.bold(
|
||||
previousVersion
|
||||
)} to ${chalk.bold(nextVersion)}`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
const sha = await getPreviousCommitSha();
|
||||
const commitLog = await getCommitLog(sha);
|
||||
|
||||
updateChangelog(nextVersion, commitLog);
|
||||
|
||||
await reviewChangelogPrompt();
|
||||
|
||||
await commitPendingChanges(previousVersion, nextVersion);
|
||||
|
||||
printFinalInstructions();
|
||||
}
|
||||
|
||||
async function commitPendingChanges(previousVersion, nextVersion) {
|
||||
console.log('');
|
||||
console.log('Committing revision and changelog.');
|
||||
console.log(chalk.dim(' git add .'));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"`
|
||||
)
|
||||
);
|
||||
|
||||
if (!DRY_RUN) {
|
||||
await exec(`
|
||||
git add .
|
||||
git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"
|
||||
`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(`Please push this commit before continuing:`);
|
||||
console.log(` ${chalk.bold.green('git push')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function getCommitLog(sha) {
|
||||
let formattedLog = '';
|
||||
|
||||
const rawLog = await execRead(`
|
||||
git log --topo-order --pretty=format:'%s' ${sha}...HEAD -- packages/react-devtools*
|
||||
`);
|
||||
rawLog.split('\n').forEach(line => {
|
||||
line = line.replace('[DevTools] ', '');
|
||||
|
||||
const match = line.match(/(.+) \(#([0-9]+)\)/);
|
||||
if (match !== null) {
|
||||
const title = match[1];
|
||||
const pr = match[2];
|
||||
|
||||
formattedLog += `\n* ${title} ([USERNAME](https://github.com/USERNAME) in [#${pr}](${PULL_REQUEST_BASE_URL}${pr}))`;
|
||||
} else {
|
||||
formattedLog += `\n* ${line}`;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedLog;
|
||||
}
|
||||
|
||||
async function getPreviousCommitSha() {
|
||||
const choices = [];
|
||||
|
||||
const lines = await execRead(`
|
||||
git log --max-count=5 --topo-order --pretty=format:'%H:::%s:::%as' HEAD -- ${PACKAGE_PATHS[0]}
|
||||
`);
|
||||
lines.split('\n').forEach((line, index) => {
|
||||
const [hash, message, date] = line.split(':::');
|
||||
choices.push({
|
||||
name: `${chalk.bold(hash)} ${chalk.dim(date)} ${message}`,
|
||||
value: hash,
|
||||
short: date,
|
||||
});
|
||||
});
|
||||
|
||||
const {sha} = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'sha',
|
||||
message: 'Which of the commits above marks the last DevTools release?',
|
||||
choices,
|
||||
default: choices[0].value,
|
||||
},
|
||||
]);
|
||||
|
||||
return sha;
|
||||
}
|
||||
|
||||
async function getReleaseType() {
|
||||
const {releaseType} = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'releaseType',
|
||||
message: 'Which type of release is this?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Minor (new user facing functionality)',
|
||||
value: 'minor',
|
||||
short: 'Minor',
|
||||
},
|
||||
{name: 'Patch (bug fixes only)', value: 'patch', short: 'Patch'},
|
||||
],
|
||||
default: 'patch',
|
||||
},
|
||||
]);
|
||||
|
||||
return releaseType;
|
||||
}
|
||||
|
||||
function printFinalInstructions() {
|
||||
const buildAndTestcriptPath = join(__dirname, 'build-and-test.js');
|
||||
const pathToPrint = relative(process.cwd(), buildAndTestcriptPath);
|
||||
|
||||
console.log('');
|
||||
console.log('Continue by running the build-and-test script:');
|
||||
console.log(chalk.bold.green(' ' + pathToPrint));
|
||||
}
|
||||
|
||||
async function reviewChangelogPrompt() {
|
||||
console.log('');
|
||||
console.log(
|
||||
'The changelog has been updated with commits since the previous release:'
|
||||
);
|
||||
console.log(` ${chalk.bold(CHANGELOG_PATH)}`);
|
||||
console.log('');
|
||||
console.log('Please review the new changelog text for the following:');
|
||||
console.log(' 1. Organize the list into Features vs Bugfixes');
|
||||
console.log(' 1. Filter out any non-user-visible changes (e.g. typo fixes)');
|
||||
console.log(' 1. Combine related PRs into a single bullet list.');
|
||||
console.log(
|
||||
' 1. Replacing the "USERNAME" placeholder text with the GitHub username(s)'
|
||||
);
|
||||
console.log('');
|
||||
console.log(` ${chalk.bold.green(`open ${CHANGELOG_PATH}`)}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
function updateChangelog(nextVersion, commitLog) {
|
||||
const path = join(ROOT_PATH, CHANGELOG_PATH);
|
||||
const oldChangelog = readFileSync(path, 'utf8');
|
||||
|
||||
const [beginning, end] = oldChangelog.split(RELEASE_SCRIPT_TOKEN);
|
||||
|
||||
const dateString = new Date().toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const header = `## ${nextVersion} (${dateString})`;
|
||||
|
||||
const newChangelog = `${beginning}${RELEASE_SCRIPT_TOKEN}\n\n${header}\n${commitLog}${end}`;
|
||||
|
||||
console.log(chalk.dim(' Updating changelog: ' + CHANGELOG_PATH));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeFileSync(path, newChangelog);
|
||||
}
|
||||
}
|
||||
|
||||
function updateManifestVersions(previousVersion, nextVersion) {
|
||||
MANIFEST_PATHS.forEach(partialPath => {
|
||||
const path = join(ROOT_PATH, partialPath);
|
||||
const json = readJsonSync(path);
|
||||
json.version = nextVersion;
|
||||
|
||||
if (json.hasOwnProperty('version_name')) {
|
||||
json.version_name = nextVersion;
|
||||
}
|
||||
|
||||
console.log(chalk.dim(' Updating manifest JSON: ' + partialPath));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeJsonSync(path, json, {spaces: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatePackageVersions(previousVersion, nextVersion) {
|
||||
PACKAGE_PATHS.forEach(partialPath => {
|
||||
const path = join(ROOT_PATH, partialPath);
|
||||
const json = readJsonSync(path);
|
||||
json.version = nextVersion;
|
||||
|
||||
for (let key in json.dependencies) {
|
||||
if (key.startsWith('react-devtools')) {
|
||||
const version = json.dependencies[key];
|
||||
|
||||
json.dependencies[key] = version.replace(previousVersion, nextVersion);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.dim(' Updating package JSON: ' + partialPath));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeJsonSync(path, json, {spaces: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
116
scripts/devtools/publish-release.js
Executable file
116
scripts/devtools/publish-release.js
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {exec} = require('child-process-promise');
|
||||
const {readJsonSync} = require('fs-extra');
|
||||
const inquirer = require('inquirer');
|
||||
const {join, relative} = require('path');
|
||||
const {DRY_RUN, NPM_PACKAGES, ROOT_PATH} = require('./configuration');
|
||||
const {
|
||||
checkNPMPermissions,
|
||||
clear,
|
||||
confirm,
|
||||
execRead,
|
||||
logger,
|
||||
readSavedBuildMetadata,
|
||||
} = require('./utils');
|
||||
|
||||
// This is the primary control function for this script.
|
||||
async function main() {
|
||||
clear();
|
||||
|
||||
await confirm('Have you run the build-and-test script?', () => {
|
||||
const buildAndTestScriptPath = join(__dirname, 'build-and-test.js');
|
||||
const pathToPrint = relative(process.cwd(), buildAndTestScriptPath);
|
||||
|
||||
console.log('Begin by running the build-and-test script:');
|
||||
console.log(chalk.bold.green(' ' + pathToPrint));
|
||||
});
|
||||
|
||||
const {archivePath, buildID} = readSavedBuildMetadata();
|
||||
|
||||
await checkNPMPermissions();
|
||||
|
||||
await publishToNPM();
|
||||
|
||||
await printFinalInstructions(buildID, archivePath);
|
||||
}
|
||||
|
||||
async function printFinalInstructions(buildID, archivePath) {
|
||||
console.log('');
|
||||
console.log(
|
||||
'You are now ready to publish the extension to Chrome, Edge, and Firefox:'
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.blue.underline(
|
||||
'https://fburl.com/publish-react-devtools-extensions'
|
||||
)}`
|
||||
);
|
||||
console.log('');
|
||||
console.log('When publishing to Firefox, remember the following:');
|
||||
console.log(` Build id: ${chalk.bold(buildID)}`);
|
||||
console.log(` Git archive: ${chalk.bold(archivePath)}`);
|
||||
console.log('');
|
||||
console.log('Also consider syncing this release to Facebook:');
|
||||
console.log(` ${chalk.bold.green('js1 upgrade react-devtools')}`);
|
||||
}
|
||||
|
||||
async function publishToNPM() {
|
||||
const {otp} = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'otp',
|
||||
message: 'Please provide an NPM two-factor auth token:',
|
||||
},
|
||||
]);
|
||||
|
||||
console.log('');
|
||||
|
||||
if (!otp) {
|
||||
console.error(chalk.red(`Invalid OTP provided: "${chalk.bold(otp)}"`));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (let index = 0; index < NPM_PACKAGES.length; index++) {
|
||||
const npmPackage = NPM_PACKAGES[index];
|
||||
const packagePath = join(ROOT_PATH, 'packages', npmPackage);
|
||||
const {version} = readJsonSync(join(packagePath, '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 ${npmPackage}@${version}`);
|
||||
if (info) {
|
||||
console.log('');
|
||||
console.log(
|
||||
`${npmPackage} version ${chalk.bold(
|
||||
version
|
||||
)} has already been published.`
|
||||
);
|
||||
|
||||
await confirm('Is this expected?');
|
||||
}
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log(`Publishing package ${chalk.bold(npmPackage)}`);
|
||||
console.log(chalk.dim(` npm publish --otp=${otp}`));
|
||||
} else {
|
||||
const publishPromise = exec(`npm publish --otp=${otp}`, {
|
||||
cwd: packagePath,
|
||||
});
|
||||
|
||||
await logger(
|
||||
publishPromise,
|
||||
`Publishing package ${chalk.bold(npmPackage)}`,
|
||||
{
|
||||
estimate: 2500,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,553 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {exec} = require('child-process-promise');
|
||||
const {readFileSync, writeFileSync} = require('fs');
|
||||
const {readJsonSync, writeJsonSync} = require('fs-extra');
|
||||
const inquirer = require('inquirer');
|
||||
const {homedir} = require('os');
|
||||
const createLogger = require('progress-estimator');
|
||||
const {join} = require('path');
|
||||
const semver = require('semver');
|
||||
|
||||
const PACKAGE_PATHS = [
|
||||
'packages/react-devtools/package.json',
|
||||
'packages/react-devtools-core/package.json',
|
||||
'packages/react-devtools-inline/package.json',
|
||||
'packages/react-devtools-scheduling-profiler/package.json',
|
||||
];
|
||||
|
||||
const MANIFEST_PATHS = [
|
||||
'packages/react-devtools-extensions/chrome/manifest.json',
|
||||
'packages/react-devtools-extensions/edge/manifest.json',
|
||||
'packages/react-devtools-extensions/firefox/manifest.json',
|
||||
];
|
||||
|
||||
const NPM_PACKAGES = [
|
||||
'react-devtools',
|
||||
'react-devtools-core',
|
||||
'react-devtools-inline',
|
||||
];
|
||||
|
||||
const CHANGELOG_PATH = 'packages/react-devtools/CHANGELOG.md';
|
||||
|
||||
const PULL_REQUEST_BASE_URL = 'https://github.com/facebook/react/pull/';
|
||||
|
||||
const RELEASE_SCRIPT_TOKEN = '<!-- RELEASE_SCRIPT_TOKEN -->';
|
||||
|
||||
const ROOT_PATH = join(__dirname, '..', '..');
|
||||
|
||||
const DRY_RUN = process.argv.includes('--dry');
|
||||
|
||||
const logger = createLogger({
|
||||
storagePath: join(__dirname, '.progress-estimator'),
|
||||
});
|
||||
|
||||
// This is the primary control function for this script.
|
||||
async function main() {
|
||||
await checkNPMPermissions();
|
||||
|
||||
const releaseType = await getReleaseType();
|
||||
|
||||
const path = join(ROOT_PATH, PACKAGE_PATHS[0]);
|
||||
const previousVersion = readJsonSync(path).version;
|
||||
const {major, minor, patch} = semver(previousVersion);
|
||||
const nextVersion =
|
||||
releaseType === 'minor'
|
||||
? `${major}.${minor + 1}.${patch}`
|
||||
: `${major}.${minor}.${patch + 1}`;
|
||||
|
||||
updatePackageVersions(previousVersion, nextVersion);
|
||||
updateManifestVersions(previousVersion, nextVersion);
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`Packages and manifests have been updated from version ${chalk.bold(
|
||||
previousVersion
|
||||
)} to ${chalk.bold(nextVersion)}`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
const sha = await getPreviousCommitSha();
|
||||
const commitLog = await getCommitLog(sha);
|
||||
|
||||
updateChangelog(nextVersion, commitLog);
|
||||
|
||||
await reviewChangelogPrompt();
|
||||
|
||||
await commitPendingChanges(previousVersion, nextVersion);
|
||||
|
||||
const archivePath = await archiveGitRevision();
|
||||
const buildID = await downloadLatestReactBuild();
|
||||
|
||||
await buildAndTestInlinePackage();
|
||||
await buildAndTestStandalonePackage();
|
||||
await buildAndTestExtensions();
|
||||
|
||||
await publishToNPM();
|
||||
|
||||
await printFinalInstructions(buildID, archivePath);
|
||||
}
|
||||
|
||||
async function archiveGitRevision() {
|
||||
const desktopPath = join(homedir(), 'Desktop');
|
||||
const archivePath = join(desktopPath, 'DevTools.tgz');
|
||||
|
||||
console.log(`Creating git archive at ${chalk.dim(archivePath)}`);
|
||||
console.log('');
|
||||
|
||||
if (!DRY_RUN) {
|
||||
await exec(`git archive main | gzip > ${archivePath}`, {cwd: ROOT_PATH});
|
||||
}
|
||||
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
async function buildAndTestExtensions() {
|
||||
const extensionsPackagePath = join(
|
||||
ROOT_PATH,
|
||||
'packages',
|
||||
'react-devtools-extensions'
|
||||
);
|
||||
const buildExtensionsPromise = exec('yarn build', {
|
||||
cwd: extensionsPackagePath,
|
||||
});
|
||||
|
||||
await logger(
|
||||
buildExtensionsPromise,
|
||||
`Building browser extensions ${chalk.dim('(this may take a minute)')}`,
|
||||
{
|
||||
estimate: 60000,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log(`Extensions have been build for Chrome, Edge, and Firefox.`);
|
||||
console.log('');
|
||||
console.log('Smoke test each extension before continuing:');
|
||||
console.log(` ${chalk.bold.green('cd ' + extensionsPackagePath)}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Test Chrome extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:chrome')}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Test Edge extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:edge')}`);
|
||||
console.log('');
|
||||
console.log(` ${chalk.dim('# Firefox Chrome extension')}`);
|
||||
console.log(` ${chalk.bold.green('yarn test:firefox')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function buildAndTestStandalonePackage() {
|
||||
const corePackagePath = join(ROOT_PATH, 'packages', 'react-devtools-core');
|
||||
const buildCorePromise = exec('yarn build', {cwd: corePackagePath});
|
||||
|
||||
await logger(
|
||||
buildCorePromise,
|
||||
`Building ${chalk.bold('react-devtools-core')} package.`,
|
||||
{
|
||||
estimate: 25000,
|
||||
}
|
||||
);
|
||||
|
||||
const standalonePackagePath = join(ROOT_PATH, 'packages', 'react-devtools');
|
||||
const safariFixturePath = join(
|
||||
ROOT_PATH,
|
||||
'fixtures',
|
||||
'devtools',
|
||||
'standalone',
|
||||
'index.html'
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
`Test the ${chalk.bold('react-devtools-core')} target before continuing:`
|
||||
);
|
||||
console.log(` ${chalk.bold.green('cd ' + standalonePackagePath)}`);
|
||||
console.log(` ${chalk.bold.green('yarn start')}`);
|
||||
console.log('');
|
||||
console.log(
|
||||
'The following fixture can be useful for testing Safari integration:'
|
||||
);
|
||||
console.log(` ${chalk.dim(safariFixturePath)}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function buildAndTestInlinePackage() {
|
||||
const inlinePackagePath = join(
|
||||
ROOT_PATH,
|
||||
'packages',
|
||||
'react-devtools-inline'
|
||||
);
|
||||
const buildPromise = exec('yarn build', {cwd: inlinePackagePath});
|
||||
|
||||
await logger(
|
||||
buildPromise,
|
||||
`Building ${chalk.bold('react-devtools-inline')} package.`,
|
||||
{
|
||||
estimate: 10000,
|
||||
}
|
||||
);
|
||||
|
||||
const shellPackagePath = join(ROOT_PATH, 'packages', 'react-devtools-shell');
|
||||
|
||||
console.log('');
|
||||
console.log(`Built ${chalk.bold('react-devtools-inline')} target.`);
|
||||
console.log('');
|
||||
console.log('Test this build before continuing:');
|
||||
console.log(` ${chalk.bold.green('cd ' + shellPackagePath)}`);
|
||||
console.log(` ${chalk.bold.green('yarn start')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function checkNPMPermissions() {
|
||||
const currentUser = await execRead('npm whoami');
|
||||
const failedProjects = [];
|
||||
|
||||
const checkProject = async project => {
|
||||
const owners = (await execRead(`npm owner ls ${project}`))
|
||||
.split('\n')
|
||||
.filter(owner => owner)
|
||||
.map(owner => owner.split(' ')[0]);
|
||||
|
||||
if (!owners.includes(currentUser)) {
|
||||
failedProjects.push(project);
|
||||
}
|
||||
};
|
||||
|
||||
await logger(
|
||||
Promise.all(NPM_PACKAGES.map(checkProject)),
|
||||
`Checking NPM permissions for ${chalk.bold(currentUser)}.`,
|
||||
{estimate: 2500}
|
||||
);
|
||||
|
||||
console.log('');
|
||||
|
||||
if (failedProjects.length) {
|
||||
console.error(chalk.bold('Insufficient NPM permissions'));
|
||||
console.error('');
|
||||
console.error(
|
||||
`NPM user {underline ${currentUser}} is not an owner for: ${chalk.bold(
|
||||
failedProjects.join(', ')
|
||||
)}`
|
||||
);
|
||||
console.error(
|
||||
'Please contact a React team member to be added to the above project(s).'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function commitPendingChanges(previousVersion, nextVersion) {
|
||||
console.log('');
|
||||
console.log('Committing revision and changelog.');
|
||||
console.log(chalk.dim(' git add .'));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"`
|
||||
)
|
||||
);
|
||||
|
||||
if (!DRY_RUN) {
|
||||
await exec(`
|
||||
git add .
|
||||
git commit -m "React DevTools ${previousVersion} -> ${nextVersion}"
|
||||
`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(`Please push this commit before continuing:`);
|
||||
console.log(` ${chalk.bold.green('git push')}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
async function confirmContinue() {
|
||||
console.log('');
|
||||
|
||||
const {confirm} = await inquirer.prompt({
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
message: 'Continue the release?',
|
||||
});
|
||||
if (!confirm) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async function downloadLatestReactBuild() {
|
||||
const releaseScriptPath = join(ROOT_PATH, 'scripts', 'release');
|
||||
const installPromise = exec('yarn install', {cwd: releaseScriptPath});
|
||||
|
||||
await logger(installPromise, 'Installing release script dependencies.', {
|
||||
estimate: 5000,
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
const {commit} = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'commit',
|
||||
message: 'Which React version (commit) should be used?',
|
||||
default: 'main',
|
||||
},
|
||||
]);
|
||||
console.log('');
|
||||
|
||||
const downloadScriptPath = join(
|
||||
releaseScriptPath,
|
||||
'download-experimental-build.js'
|
||||
);
|
||||
const downloadPromise = execRead(
|
||||
`"${downloadScriptPath}" --commit=${commit}`
|
||||
);
|
||||
|
||||
const output = await logger(
|
||||
downloadPromise,
|
||||
'Downloading React artifacts from CI.',
|
||||
{estimate: 15000}
|
||||
);
|
||||
|
||||
const match = output.match('--build=([0-9]+)');
|
||||
if (match.length === 0) {
|
||||
console.error(`No build ID found in "${output}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const buildID = match[1];
|
||||
|
||||
console.log('');
|
||||
console.log(`Downloaded artiacts for CI build ${chalk.bold(buildID)}.`);
|
||||
|
||||
await confirmContinue();
|
||||
|
||||
return buildID;
|
||||
}
|
||||
|
||||
async function execRead(command, options) {
|
||||
const {stdout} = await exec(command, options);
|
||||
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
async function getCommitLog(sha) {
|
||||
let formattedLog = '';
|
||||
|
||||
const rawLog = await execRead(`
|
||||
git log --topo-order --pretty=format:'%s' ${sha}...HEAD -- packages/react-devtools*
|
||||
`);
|
||||
rawLog.split('\n').forEach(line => {
|
||||
line = line.replace('[DevTools] ', '');
|
||||
|
||||
const match = line.match(/(.+) \(#([0-9]+)\)/);
|
||||
if (match !== null) {
|
||||
const title = match[1];
|
||||
const pr = match[2];
|
||||
|
||||
formattedLog += `\n* ${title} ([USERNAME](https://github.com/USERNAME) in [#${pr}](${PULL_REQUEST_BASE_URL}${pr}))`;
|
||||
} else {
|
||||
formattedLog += `\n* ${line}`;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedLog;
|
||||
}
|
||||
|
||||
async function getPreviousCommitSha() {
|
||||
const choices = [];
|
||||
|
||||
const lines = await execRead(`
|
||||
git log --max-count=5 --topo-order --pretty=format:'%H:::%s:::%as' HEAD -- ${PACKAGE_PATHS[0]}
|
||||
`);
|
||||
lines.split('\n').forEach((line, index) => {
|
||||
const [hash, message, date] = line.split(':::');
|
||||
choices.push({
|
||||
name: `${chalk.bold(hash)} ${chalk.dim(date)} ${message}`,
|
||||
value: hash,
|
||||
short: date,
|
||||
});
|
||||
});
|
||||
|
||||
const {sha} = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'sha',
|
||||
message: 'Which of the commits above marks the last DevTools release?',
|
||||
choices,
|
||||
default: choices[0].value,
|
||||
},
|
||||
]);
|
||||
|
||||
return sha;
|
||||
}
|
||||
|
||||
async function getReleaseType() {
|
||||
const {releaseType} = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'releaseType',
|
||||
message: 'Which type of release is this?',
|
||||
choices: [
|
||||
{
|
||||
name: 'Minor (new user facing functionality)',
|
||||
value: 'minor',
|
||||
short: 'Minor',
|
||||
},
|
||||
{name: 'Patch (bug fixes only)', value: 'patch', short: 'Patch'},
|
||||
],
|
||||
default: 'patch',
|
||||
},
|
||||
]);
|
||||
|
||||
return releaseType;
|
||||
}
|
||||
|
||||
async function printFinalInstructions(buildID, archivePath) {
|
||||
console.log('');
|
||||
console.log(
|
||||
'You are now ready to publish the extension to Chrome, Edge, and Firefox:'
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.blue.underline(
|
||||
'https://fburl.com/publish-react-devtools-extensions'
|
||||
)}`
|
||||
);
|
||||
console.log('');
|
||||
console.log('When publishing to Firefox, remember the following:');
|
||||
console.log(` Build id: ${chalk.bold(buildID)}`);
|
||||
console.log(` Git archive: ${chalk.bold(archivePath)}`);
|
||||
console.log('');
|
||||
console.log('Also consider syncing this release to Facebook:');
|
||||
console.log(` ${chalk.bold.green('js1 upgrade react-devtools')}`);
|
||||
}
|
||||
|
||||
async function publishToNPM() {
|
||||
const {otp} = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'otp',
|
||||
message: 'Please provide an NPM two-factor auth token:',
|
||||
},
|
||||
]);
|
||||
|
||||
console.log('');
|
||||
|
||||
if (!otp) {
|
||||
console.error(`Invalid OTP provided: "${chalk.bold(otp)}"`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (let index = 0; index < NPM_PACKAGES.length; index++) {
|
||||
const npmPackage = NPM_PACKAGES[index];
|
||||
const packagePath = join(ROOT_PATH, 'packages', npmPackage);
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log(`Publishing package ${chalk.bold(npmPackage)}`);
|
||||
console.log(chalk.dim(` npm publish --otp=${otp}`));
|
||||
} else {
|
||||
const publishPromise = exec(`npm publish --otp=${otp}`, {
|
||||
cwd: packagePath,
|
||||
});
|
||||
|
||||
await logger(
|
||||
publishPromise,
|
||||
`Publishing package ${chalk.bold(npmPackage)}`,
|
||||
{
|
||||
estimate: 2500,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reviewChangelogPrompt() {
|
||||
console.log('');
|
||||
console.log(
|
||||
'The changelog has been updated with commits since the previous release:'
|
||||
);
|
||||
console.log(` ${chalk.bold(CHANGELOG_PATH)}`);
|
||||
console.log('');
|
||||
console.log('Please review the new changelog text for the following:');
|
||||
console.log(' 1. Organize the list into Features vs Bugfixes');
|
||||
console.log(' 1. Filter out any non-user-visible changes (e.g. typo fixes)');
|
||||
console.log(' 1. Combine related PRs into a single bullet list.');
|
||||
console.log(
|
||||
' 1. Replacing the "USERNAME" placeholder text with the GitHub username(s)'
|
||||
);
|
||||
console.log('');
|
||||
console.log(` ${chalk.bold.green(`open ${CHANGELOG_PATH}`)}`);
|
||||
|
||||
await confirmContinue();
|
||||
}
|
||||
|
||||
function updateChangelog(nextVersion, commitLog) {
|
||||
const path = join(ROOT_PATH, CHANGELOG_PATH);
|
||||
const oldChangelog = readFileSync(path, 'utf8');
|
||||
|
||||
const [beginning, end] = oldChangelog.split(RELEASE_SCRIPT_TOKEN);
|
||||
|
||||
const dateString = new Date().toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const header = `## ${nextVersion} (${dateString})`;
|
||||
|
||||
const newChangelog = `${beginning}${RELEASE_SCRIPT_TOKEN}\n\n${header}\n${commitLog}${end}`;
|
||||
|
||||
console.log(chalk.dim(' Updating changelog: ' + CHANGELOG_PATH));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeFileSync(path, newChangelog);
|
||||
}
|
||||
}
|
||||
|
||||
function updateManifestVersions(previousVersion, nextVersion) {
|
||||
MANIFEST_PATHS.forEach(partialPath => {
|
||||
const path = join(ROOT_PATH, partialPath);
|
||||
const json = readJsonSync(path);
|
||||
json.version = nextVersion;
|
||||
|
||||
if (json.hasOwnProperty('version_name')) {
|
||||
json.version_name = nextVersion;
|
||||
}
|
||||
|
||||
console.log(chalk.dim(' Updating manifest JSON: ' + partialPath));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeJsonSync(path, json, {spaces: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updatePackageVersions(previousVersion, nextVersion) {
|
||||
PACKAGE_PATHS.forEach(partialPath => {
|
||||
const path = join(ROOT_PATH, partialPath);
|
||||
const json = readJsonSync(path);
|
||||
json.version = nextVersion;
|
||||
|
||||
for (let key in json.dependencies) {
|
||||
if (key.startsWith('react-devtools')) {
|
||||
const version = json.dependencies[key];
|
||||
|
||||
json.dependencies[key] = version.replace(previousVersion, nextVersion);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.dim(' Updating package JSON: ' + partialPath));
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeJsonSync(path, json, {spaces: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
128
scripts/devtools/utils.js
Normal file
128
scripts/devtools/utils.js
Normal file
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {exec} = require('child-process-promise');
|
||||
const {existsSync, mkdirSync} = require('fs');
|
||||
const {readJsonSync, writeJsonSync} = require('fs-extra');
|
||||
const inquirer = require('inquirer');
|
||||
const {join} = require('path');
|
||||
const createLogger = require('progress-estimator');
|
||||
const {
|
||||
BUILD_METADATA_TEMP_DIRECTORY,
|
||||
NPM_PACKAGES,
|
||||
} = require('./configuration');
|
||||
|
||||
const logger = createLogger({
|
||||
storagePath: join(__dirname, '.progress-estimator'),
|
||||
});
|
||||
|
||||
async function checkNPMPermissions() {
|
||||
const currentUser = await execRead('npm whoami');
|
||||
const failedProjects = [];
|
||||
|
||||
const checkProject = async project => {
|
||||
const owners = (await execRead(`npm owner ls ${project}`))
|
||||
.split('\n')
|
||||
.filter(owner => owner)
|
||||
.map(owner => owner.split(' ')[0]);
|
||||
|
||||
if (!owners.includes(currentUser)) {
|
||||
failedProjects.push(project);
|
||||
}
|
||||
};
|
||||
|
||||
await logger(
|
||||
Promise.all(NPM_PACKAGES.map(checkProject)),
|
||||
`Checking NPM permissions for ${chalk.bold(currentUser)}.`,
|
||||
{estimate: 2500}
|
||||
);
|
||||
|
||||
console.log('');
|
||||
|
||||
if (failedProjects.length) {
|
||||
console.error(chalk.red.bold('Insufficient NPM permissions'));
|
||||
console.error('');
|
||||
console.error(
|
||||
chalk.red(
|
||||
`NPM user {underline ${currentUser}} is not an owner for: ${chalk.bold(
|
||||
failedProjects.join(', ')
|
||||
)}`
|
||||
)
|
||||
);
|
||||
console.error(
|
||||
chalk.red(
|
||||
'Please contact a React team member to be added to the above project(s).'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
console.clear();
|
||||
}
|
||||
|
||||
async function confirm(message, exitFunction) {
|
||||
console.log('');
|
||||
|
||||
const {confirmation} = await inquirer.prompt({
|
||||
name: 'confirmation',
|
||||
type: 'confirm',
|
||||
message,
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
if (!confirmation) {
|
||||
if (typeof exitFunction === 'function') {
|
||||
exitFunction();
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmContinue(exitFunction) {
|
||||
await confirm('Continue the release?', exitFunction);
|
||||
}
|
||||
|
||||
async function execRead(command, options) {
|
||||
const {stdout} = await exec(command, options);
|
||||
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
function readSavedBuildMetadata() {
|
||||
const path = join(BUILD_METADATA_TEMP_DIRECTORY, 'metadata');
|
||||
|
||||
if (!existsSync(path)) {
|
||||
console.error(chalk.red('Expected to find build metadata at:'));
|
||||
console.error(chalk.dim(` ${path}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const {archivePath, buildID} = readJsonSync(path);
|
||||
|
||||
return {archivePath, buildID};
|
||||
}
|
||||
|
||||
function saveBuildMetadata({archivePath, buildID}) {
|
||||
const path = join(BUILD_METADATA_TEMP_DIRECTORY, 'metadata');
|
||||
|
||||
if (!existsSync(BUILD_METADATA_TEMP_DIRECTORY)) {
|
||||
mkdirSync(BUILD_METADATA_TEMP_DIRECTORY);
|
||||
}
|
||||
|
||||
writeJsonSync(path, {archivePath, buildID}, {spaces: 2});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkNPMPermissions,
|
||||
clear,
|
||||
confirm,
|
||||
confirmContinue,
|
||||
execRead,
|
||||
logger,
|
||||
readSavedBuildMetadata,
|
||||
saveBuildMetadata,
|
||||
};
|
||||
Reference in New Issue
Block a user