Release script supports publishing a subset of packages (#16338)
Release script supports publishing a subset of packages (#16338)
This commit is contained in:
parent
0bd0c5269f
commit
5b007573ac
|
@ -21,5 +21,5 @@ const run = async ({cwd, dry, tempDirectory}) => {
|
|||
};
|
||||
|
||||
module.exports = async params => {
|
||||
return logPromise(run(params), 'Building artifacts', 420000);
|
||||
return logPromise(run(params), 'Building artifacts', 600000);
|
||||
};
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
const prompt = require('prompt-promise');
|
||||
const semver = require('semver');
|
||||
const theme = require('../theme');
|
||||
const {confirm} = require('../utils');
|
||||
|
||||
const run = async (params, versionsMap) => {
|
||||
const run = async ({skipPackages}, versionsMap) => {
|
||||
const groupedVersionsMap = new Map();
|
||||
|
||||
// Group packages with the same source versions.
|
||||
|
@ -26,14 +27,26 @@ const run = async (params, versionsMap) => {
|
|||
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();
|
||||
|
||||
let version = bestGuessVersion;
|
||||
if (
|
||||
skipPackages.some(skipPackageName =>
|
||||
packageNames.includes(skipPackageName)
|
||||
)
|
||||
) {
|
||||
await confirm(
|
||||
theme`{spinnerSuccess ✓} Version for ${packageNames} will remain {version ${bestGuessVersion}}`
|
||||
);
|
||||
} else {
|
||||
const defaultVersion = bestGuessVersion
|
||||
? theme.version(` (default ${bestGuessVersion})`)
|
||||
: '';
|
||||
version =
|
||||
(await prompt(
|
||||
theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: `
|
||||
)) || bestGuessVersion;
|
||||
prompt.done();
|
||||
}
|
||||
|
||||
// Verify a valid version has been supplied.
|
||||
try {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
const semver = require('semver');
|
||||
const {execRead, logPromise} = require('../utils');
|
||||
|
||||
const run = async ({cwd, packages}, versionsMap) => {
|
||||
const run = async ({cwd, packages, skipPackages}, versionsMap) => {
|
||||
const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
|
||||
cwd,
|
||||
});
|
||||
|
@ -17,16 +17,21 @@ const run = async ({cwd, packages}, versionsMap) => {
|
|||
// 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.
|
||||
// By default, new releases from masters should increment the minor version number,
|
||||
// and patch releases should be done from branches.
|
||||
if (branch === 'master') {
|
||||
versionsMap.set(packageName, `${major}.${minor + 1}.0`);
|
||||
if (skipPackages.includes(packageName)) {
|
||||
versionsMap.set(packageName, version);
|
||||
} else {
|
||||
versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
|
||||
const {major, minor, patch} = semver(version);
|
||||
|
||||
// Guess the next version by incrementing patch.
|
||||
// The script will confirm this later.
|
||||
// By default, new releases from masters should increment the minor version number,
|
||||
// and patch releases should be done from branches.
|
||||
if (branch === 'master') {
|
||||
versionsMap.set(packageName, `${major}.${minor + 1}.0`);
|
||||
} else {
|
||||
versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If the package has not yet been published,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
'use strict';
|
||||
|
||||
const commandLineArgs = require('command-line-args');
|
||||
const {splitCommaParams} = require('../utils');
|
||||
|
||||
const paramDefinitions = [
|
||||
{
|
||||
|
@ -12,6 +13,13 @@ const paramDefinitions = [
|
|||
'Skip NPM and use the build already present in "build/node_modules".',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'skipPackages',
|
||||
type: String,
|
||||
multiple: true,
|
||||
description: 'Packages to exclude from publishing',
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
name: 'skipTests',
|
||||
type: Boolean,
|
||||
|
@ -28,5 +36,7 @@ const paramDefinitions = [
|
|||
module.exports = () => {
|
||||
const params = commandLineArgs(paramDefinitions);
|
||||
|
||||
splitCommaParams(params.skipPackages);
|
||||
|
||||
return params;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const clear = require('clear');
|
||||
const {confirm} = require('../utils');
|
||||
const theme = require('../theme');
|
||||
|
||||
const run = async ({cwd, packages, skipPackages, tags}) => {
|
||||
if (skipPackages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
console.log(
|
||||
theme`{spinnerSuccess ✓} The following packages will not be published as part of this release`
|
||||
);
|
||||
|
||||
skipPackages.forEach(packageName => {
|
||||
console.log(theme`• {package ${packageName}}`);
|
||||
});
|
||||
|
||||
await confirm('Do you want to proceed?');
|
||||
|
||||
clear();
|
||||
};
|
||||
|
||||
// Run this directly because it's fast,
|
||||
// and logPromise would interfere with console prompting.
|
||||
module.exports = run;
|
|
@ -23,7 +23,6 @@ const run = async ({cwd, packages, tags}) => {
|
|||
);
|
||||
}
|
||||
|
||||
// Cache all package JSONs for easy lookup below.
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
const packageName = packages[i];
|
||||
const packageJSONPath = join(
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
const {exec} = require('child-process-promise');
|
||||
const {readJsonSync} = require('fs-extra');
|
||||
const {join} = require('path');
|
||||
const {getArtifactsList, logPromise} = require('../utils');
|
||||
const theme = require('../theme');
|
||||
|
||||
const run = async ({cwd, tags}) => {
|
||||
const run = async ({cwd, packages, tags}) => {
|
||||
if (!tags.includes('latest')) {
|
||||
// Don't update error-codes for alphas.
|
||||
return;
|
||||
|
@ -15,8 +16,9 @@ const run = async ({cwd, tags}) => {
|
|||
|
||||
// All packages are built from a single source revision,
|
||||
// so it is safe to read build info from any one of them.
|
||||
const arbitraryPackageName = packages[0];
|
||||
const {buildNumber, environment} = readJsonSync(
|
||||
`${cwd}/build/node_modules/react/build-info.json`
|
||||
join(cwd, 'build', 'node_modules', arbitraryPackageName, 'build-info.json')
|
||||
);
|
||||
|
||||
// If this release was created on Circle CI, grab the updated error codes from there.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
const commandLineArgs = require('command-line-args');
|
||||
const commandLineUsage = require('command-line-usage');
|
||||
const {splitCommaParams} = require('../utils');
|
||||
|
||||
const paramDefinitions = [
|
||||
{
|
||||
|
@ -18,12 +19,21 @@ const paramDefinitions = [
|
|||
multiple: true,
|
||||
description: 'NPM tags to point to the new release.',
|
||||
},
|
||||
{
|
||||
name: 'skipPackages',
|
||||
type: String,
|
||||
multiple: true,
|
||||
description: 'Packages to exclude from publishing',
|
||||
defaultValue: [],
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = () => {
|
||||
const params = commandLineArgs(paramDefinitions);
|
||||
|
||||
if (!params.tags || params.tags.length === 0) {
|
||||
const {skipPackages, tags} = params;
|
||||
|
||||
if (!tags || tags.length === 0) {
|
||||
const usage = commandLineUsage([
|
||||
{
|
||||
content:
|
||||
|
@ -51,5 +61,8 @@ module.exports = () => {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
splitCommaParams(skipPackages);
|
||||
splitCommaParams(tags);
|
||||
|
||||
return params;
|
||||
};
|
||||
|
|
|
@ -11,9 +11,10 @@ const {execRead} = require('../utils');
|
|||
|
||||
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.
|
||||
// so it is safe to read build info from any one of them.
|
||||
const arbitraryPackageName = packages[0];
|
||||
const {commit, environment} = readJsonSync(
|
||||
`${cwd}/build/node_modules/react/build-info.json`
|
||||
join(cwd, 'build', 'node_modules', arbitraryPackageName, 'build-info.json')
|
||||
);
|
||||
|
||||
// Tags are named after the react version.
|
||||
|
@ -50,7 +51,13 @@ const run = async ({cwd, packages, tags}) => {
|
|||
const packageName = packages[i];
|
||||
console.log(theme.path`• packages/%s/package.json`, packageName);
|
||||
}
|
||||
console.log(theme.path`• packages/shared/ReactVersion.js`);
|
||||
const status = await execRead(
|
||||
'git diff packages/shared/ReactVersion.js',
|
||||
{cwd}
|
||||
);
|
||||
if (status) {
|
||||
console.log(theme.path`• packages/shared/ReactVersion.js`);
|
||||
}
|
||||
|
||||
console.log();
|
||||
if (environment === 'ci') {
|
||||
|
|
|
@ -6,7 +6,7 @@ const {readFileSync, writeFileSync} = require('fs');
|
|||
const {readJson, writeJson} = require('fs-extra');
|
||||
const {join} = require('path');
|
||||
|
||||
const run = async ({cwd, packages, tags}) => {
|
||||
const run = async ({cwd, packages, skipPackages, tags}) => {
|
||||
if (!tags.includes('latest')) {
|
||||
// Don't update version numbers for alphas.
|
||||
return;
|
||||
|
@ -35,15 +35,18 @@ const run = async ({cwd, packages, tags}) => {
|
|||
}
|
||||
|
||||
// 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);
|
||||
// (Unless this release does not include an update to React)
|
||||
if (!skipPackages.includes('react')) {
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const {readJson} = require('fs-extra');
|
||||
const {join} = require('path');
|
||||
const theme = require('../theme');
|
||||
const {execRead} = require('../utils');
|
||||
|
||||
const readPackageJSON = async (cwd, name) => {
|
||||
const packageJSONPath = join(
|
||||
cwd,
|
||||
'build',
|
||||
'node_modules',
|
||||
name,
|
||||
'package.json'
|
||||
);
|
||||
return await readJson(packageJSONPath);
|
||||
};
|
||||
|
||||
const run = async ({cwd, packages, skipPackages}) => {
|
||||
if (skipPackages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validateDependencies = async (name, dependencies) => {
|
||||
if (!dependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let dependency in dependencies) {
|
||||
// Do we depend on a package thas has been skipped?
|
||||
if (skipPackages.includes(dependency)) {
|
||||
const version = dependencies[dependency];
|
||||
// Do we depend on a version of the package than has not been published to NPM?
|
||||
const info = await execRead(`npm view ${dependency}@${version}`);
|
||||
if (!info) {
|
||||
console.log(
|
||||
theme`{error Package} {package ${name}} {error depends on an unpublished skipped package}`,
|
||||
theme`{package ${dependency}}@{version ${version}}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure none of the other packages depend on a skipped package,
|
||||
// unless the dependency has already been published to NPM.
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
const name = packages[i];
|
||||
const {dependencies, peerDependencies} = await readPackageJSON(cwd, name);
|
||||
|
||||
validateDependencies(name, dependencies);
|
||||
validateDependencies(name, peerDependencies);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = run;
|
|
@ -8,11 +8,13 @@ const theme = require('../theme');
|
|||
|
||||
const run = async ({cwd, packages, tags}) => {
|
||||
// Prevent a canary release from ever being published as @latest
|
||||
// All canaries share a version number, so it's okay to check any of them.
|
||||
const arbitraryPackageName = packages[0];
|
||||
const packageJSONPath = join(
|
||||
cwd,
|
||||
'build',
|
||||
'node_modules',
|
||||
'react',
|
||||
arbitraryPackageName,
|
||||
'package.json'
|
||||
);
|
||||
const {version} = await readJson(packageJSONPath);
|
||||
|
@ -23,6 +25,13 @@ const run = async ({cwd, packages, tags}) => {
|
|||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
if (tags.includes('canary')) {
|
||||
console.log(
|
||||
theme`{error Stable release} {version ${version}} {error cannot be tagged as} {tag canary}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
const {join} = require('path');
|
||||
const {getPublicPackages, handleError} = require('./utils');
|
||||
const theme = require('./theme');
|
||||
|
||||
const checkNPMPermissions = require('./publish-commands/check-npm-permissions');
|
||||
const confirmSkippedPackages = require('./publish-commands/confirm-skipped-packages');
|
||||
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');
|
||||
|
@ -14,6 +16,7 @@ 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');
|
||||
const validateSkipPackages = require('./publish-commands/validate-skip-packages');
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
|
@ -21,8 +24,24 @@ const run = async () => {
|
|||
params.cwd = join(__dirname, '..', '..');
|
||||
params.packages = await getPublicPackages();
|
||||
|
||||
// Pre-filter any skipped packages to simplify the following commands.
|
||||
// As part of doing this we can also validate that none of the skipped packages were misspelled.
|
||||
params.skipPackages.forEach(packageName => {
|
||||
const index = params.packages.indexOf(packageName);
|
||||
if (index < 0) {
|
||||
console.log(
|
||||
theme`Invalid skip package {package ${packageName}} specified.`
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
params.packages.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
await validateTags(params);
|
||||
await confirmSkippedPackages(params);
|
||||
await confirmVersionAndTags(params);
|
||||
await validateSkipPackages(params);
|
||||
await checkNPMPermissions(params);
|
||||
const otp = await promptForOTP(params);
|
||||
await publishToNPM(params, otp);
|
||||
|
|
|
@ -157,6 +157,17 @@ const printDiff = (path, beforeContents, afterContents) => {
|
|||
return patch;
|
||||
};
|
||||
|
||||
// Convert an array param (expected format "--foo bar baz")
|
||||
// to also accept comma input (e.g. "--foo bar,baz")
|
||||
const splitCommaParams = array => {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
const param = array[i];
|
||||
if (param.includes(',')) {
|
||||
array.splice(i, 1, ...param.split(','));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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".
|
||||
|
@ -228,6 +239,7 @@ module.exports = {
|
|||
handleError,
|
||||
logPromise,
|
||||
printDiff,
|
||||
splitCommaParams,
|
||||
theme,
|
||||
updateVersionsForCanary,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue