react/compiler/scripts/release/publish.js
2024-08-06 15:35:38 -04:00

210 lines
6.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const ora = require('ora');
const path = require('path');
const yargs = require('yargs');
const {hashElement} = require('folder-hash');
const promptForOTP = require('./prompt-for-otp');
const {PUBLISHABLE_PACKAGES} = require('./shared/packages');
const {
execHelper,
getDateStringForCommit,
spawnHelper,
} = require('./shared/utils');
const {buildPackages} = require('./shared/build-packages');
const {readJson, writeJson} = require('fs-extra');
/**
* Script for publishing PUBLISHABLE_PACKAGES to npm. By default, this runs in tarball mode, meaning
* the script will only print out what the contents of the files included in the npm tarball would
* be.
*
* Please run this first (ie `yarn npm:publish`) and double check the contents of the files that
* will be pushed to npm.
*
* If it looks good, you can run `yarn npm:publish --for-real` to really publish to npm. You must
* have 2FA enabled first and the script will prompt you to enter a 2FA code before proceeding.
* There's a small annoying delay before the packages are actually pushed to give you time to panic
* cancel. In this mode, we will bump the version field of each package's package.json, and git
* commit it. Then, the packages will be published to npm.
*
* Optionally, you can add the `--debug` flag to `yarn npm:publish --debug --for-real` to run all
* steps, but the final npm publish step will have the `--dry-run` flag added to it. This will make
* the command only report what it would have done, instead of actually publishing to npm.
*/
async function main() {
const argv = yargs(process.argv.slice(2))
.option('packages', {
description: 'which packages to publish, defaults to all',
choices: PUBLISHABLE_PACKAGES,
default: PUBLISHABLE_PACKAGES,
})
.option('for-real', {
alias: 'frfr',
description:
'whether to publish to npm (npm publish) or dryrun (npm publish --dry-run)',
type: 'boolean',
default: false,
})
.option('debug', {
description:
'If enabled, will always run npm commands in dry run mode irregardless of the for-real flag',
type: 'boolean',
default: false,
})
.option('ci', {
description: 'Publish packages via CI',
type: 'boolean',
default: false,
})
.option('tags', {
description: 'Tags to publish to npm',
type: 'choices',
choices: ['experimental'],
default: ['experimental'],
})
.help('help')
.parseSync();
if (argv.debug === false) {
const currBranchName = await execHelper('git rev-parse --abbrev-ref HEAD');
const isPristine = (await execHelper('git status --porcelain')) === '';
if (currBranchName !== 'main' || isPristine === false) {
throw new Error(
'This script must be run from the `main` branch with no uncommitted changes'
);
}
}
let pkgNames = argv.packages;
if (Array.isArray(argv.packages) === false) {
pkgNames = [argv.packages];
}
const spinner = ora(
`Preparing to publish ${
argv.forReal === true ? '(for real)' : '(dry run)'
} [debug=${argv.debug}]`
).info();
await buildPackages(pkgNames);
if (argv.forReal === false) {
spinner.info('Dry run: Report tarball contents');
for (const pkgName of pkgNames) {
console.log(`\n========== ${pkgName} ==========\n`);
spinner.start(`Running npm pack --dry-run\n`);
try {
await spawnHelper('npm', ['pack', '--dry-run'], {
cwd: path.resolve(__dirname, `../../packages/${pkgName}`),
stdio: 'inherit',
});
} catch (e) {
spinner.fail(e.toString());
throw e;
}
spinner.stop(`Successfully packed ${pkgName} (dry run)`);
}
spinner.succeed(
'Please confirm contents of packages before publishing. You can run this command again with --for-real to publish to npm'
);
}
if (argv.forReal === true) {
const commit = await execHelper(
'git show -s --no-show-signature --format=%h',
{
cwd: path.resolve(__dirname, '..'),
}
);
const dateString = await getDateStringForCommit(commit);
const otp = argv.ci === false ? await promptForOTP() : null;
for (const pkgName of pkgNames) {
const pkgDir = path.resolve(__dirname, `../../packages/${pkgName}`);
const pkgJsonPath = path.resolve(
__dirname,
`../../packages/${pkgName}/package.json`
);
const {hash} = await hashElement(pkgDir, {
encoding: 'hex',
folders: {exclude: ['node_modules']},
files: {exclude: ['.DS_Store']},
});
const truncatedHash = hash.slice(0, 7);
const newVersion = `0.0.0-experimental-${truncatedHash}-${dateString}`;
spinner.start(`Writing package.json for ${pkgName}@${newVersion}`);
await writeJson(
pkgJsonPath,
{
...(await readJson(pkgJsonPath)),
version: newVersion,
},
{spaces: 2}
);
spinner.succeed(`Wrote package.json for ${pkgName}@${newVersion}`);
console.log(`\n========== ${pkgName} ==========\n`);
spinner.start(`Publishing ${pkgName}@${newVersion} to npm\n`);
let opts = [];
if (argv.debug === true) {
opts.push('--dry-run');
}
if (otp != null) {
opts.push(`--otp=${otp}`);
}
try {
await spawnHelper(
'npm',
[
'publish',
...opts,
'--registry=https://registry.npmjs.org',
'--tag=experimental',
],
{
cwd: pkgDir,
stdio: 'inherit',
}
);
console.log('\n');
} catch (e) {
spinner.fail(e.toString());
throw e;
}
spinner.succeed(`Successfully published ${pkgName} to npm`);
spinner.start('Pushing tags to npm');
for (const tag of argv.tags) {
try {
let opts = ['dist-tag', 'add', `${pkgName}@${newVersion}`, tag];
if (otp != null) {
opts.push(`--otp=${otp}`);
}
if (argv.debug === true) {
spinner.info(`dry-run: npm ${opts.join(' ')}`);
} else {
await spawnHelper('npm', opts, {
cwd: pkgDir,
stdio: 'inherit',
});
}
} catch (e) {
spinner.fail(e.toString());
throw e;
}
spinner.succeed(
`Successfully pushed dist-tag ${tag} for ${pkgName} to npm`
);
}
}
console.log('\n\n✅ All done');
}
}
main();