compiler/scripts/release/publish.js JAVASCRIPT 238 lines View on github.com → Search inside
1#!/usr/bin/env node2/**3 * Copyright (c) Meta Platforms, Inc. and affiliates.4 *5 * This source code is licensed under the MIT license found in the6 * LICENSE file in the root directory of this source tree.7 */89'use strict';1011const ora = require('ora');12const path = require('path');13const yargs = require('yargs');14const {hashElement} = require('folder-hash');15const promptForOTP = require('./prompt-for-otp');16const {PUBLISHABLE_PACKAGES} = require('./shared/packages');17const {18  execHelper,19  getDateStringForCommit,20  spawnHelper,21} = require('./shared/utils');22const {buildPackages} = require('./shared/build-packages');23const {readJson, writeJson} = require('fs-extra');2425/**26 * Script for publishing PUBLISHABLE_PACKAGES to npm. By default, this runs in tarball mode, meaning27 * the script will only print out what the contents of the files included in the npm tarball would28 * be.29 *30 * Please run this first (ie `yarn npm:publish`) and double check the contents of the files that31 * will be pushed to npm.32 *33 * If it looks good, you can run `yarn npm:publish --for-real` to really publish to npm. You must34 * have 2FA enabled first and the script will prompt you to enter a 2FA code before proceeding.35 * There's a small annoying delay before the packages are actually pushed to give you time to panic36 * cancel. In this mode, we will bump the version field of each package's package.json, and git37 * commit it. Then, the packages will be published to npm.38 *39 * Optionally, you can add the `--debug` flag to `yarn npm:publish --debug --for-real` to run all40 * steps, but the final npm publish step will have the `--dry-run` flag added to it. This will make41 * the command only report what it would have done, instead of actually publishing to npm.42 */43async function main() {44  const argv = yargs(process.argv.slice(2))45    .option('packages', {46      description: 'which packages to publish, defaults to all',47      choices: PUBLISHABLE_PACKAGES,48      default: PUBLISHABLE_PACKAGES,49    })50    .option('for-real', {51      alias: 'frfr',52      description:53        'whether to publish to npm (npm publish) or dryrun (npm publish --dry-run)',54      type: 'boolean',55      default: false,56    })57    .option('debug', {58      description:59        'If enabled, will always run npm commands in dry run mode irregardless of the for-real flag',60      type: 'boolean',61      default: false,62    })63    .option('ci', {64      description: 'Publish packages via CI',65      type: 'boolean',66      default: false,67    })68    .option('tag', {69      description: 'Tag to publish to npm',70      type: 'choices',71      choices: ['experimental', 'beta', 'rc', 'latest'],72      default: 'experimental',73    })74    .option('tag-version', {75      description:76        'Optional tag version to append to tag name, eg `1` becomes 0.0.0-rc.1',77      type: 'number',78      default: null,79    })80    .option('version-name', {81      description: 'Version name',82      type: 'string',83      default: '0.0.0',84    })85    .help('help')86    .strict()87    .parseSync();8889  if (argv.debug === false) {90    const currBranchName = await execHelper('git rev-parse --abbrev-ref HEAD');91    const isPristine = (await execHelper('git status --porcelain')) === '';92    if (currBranchName !== 'main' || isPristine === false) {93      throw new Error(94        'This script must be run from the `main` branch with no uncommitted changes'95      );96    }97  }9899  let pkgNames = argv.packages;100  if (Array.isArray(argv.packages) === false) {101    pkgNames = [argv.packages];102  }103  const spinner = ora(104    `Preparing to publish ${argv.versionName}@${argv.tag} ${105      argv.forReal === true ? '(for real)' : '(dry run)'106    } [debug=${argv.debug}]`107  ).info();108109  await buildPackages(pkgNames);110111  if (argv.forReal === false) {112    spinner.info('Dry run: Report tarball contents');113    for (const pkgName of pkgNames) {114      console.log(`\n========== ${pkgName} ==========\n`);115      spinner.start(`Running npm pack --dry-run\n`);116      try {117        await spawnHelper('npm', ['pack', '--dry-run'], {118          cwd: path.resolve(__dirname, `../../packages/${pkgName}`),119          stdio: 'inherit',120        });121      } catch (e) {122        spinner.fail(e.toString());123        throw e;124      }125      spinner.stop(`Successfully packed ${pkgName} (dry run)`);126    }127    spinner.succeed(128      'Please confirm contents of packages before publishing. You can run this command again with --for-real to publish to npm'129    );130  }131132  if (argv.forReal === true) {133    const commit = await execHelper(134      'git show -s --no-show-signature --format=%h',135      {136        cwd: path.resolve(__dirname, '..'),137      }138    );139    const dateString = await getDateStringForCommit(commit);140    const otp =141      argv.ci === false && argv.debug === false ? await promptForOTP() : null;142    const {hash} = await hashElement(path.resolve(__dirname, '../..'), {143      encoding: 'hex',144      folders: {exclude: ['node_modules']},145      files: {exclude: ['.DS_Store']},146    });147    const truncatedHash = hash.slice(0, 7);148    let newVersion;149    if (argv.tag === 'latest') {150      newVersion = argv.versionName;151    } else {152      newVersion =153        argv.tagVersion == null || argv.tagVersion === ''154          ? `${argv.versionName}-${argv.tag}`155          : `${argv.versionName}-${argv.tag}.${argv.tagVersion}`;156    }157    if (argv.tag === 'experimental' || argv.tag === 'beta') {158      newVersion = `${newVersion}-${truncatedHash}-${dateString}`;159    }160161    for (const pkgName of pkgNames) {162      const pkgDir = path.resolve(__dirname, `../../packages/${pkgName}`);163      const pkgJsonPath = path.resolve(164        __dirname,165        `../../packages/${pkgName}/package.json`166      );167168      spinner.start(`Writing package.json for ${pkgName}@${newVersion}`);169      await writeJson(170        pkgJsonPath,171        {172          ...(await readJson(pkgJsonPath)),173          version: newVersion,174        },175        {spaces: 2}176      );177      spinner.succeed(`Wrote package.json for ${pkgName}@${newVersion}`);178179      console.log(`\n========== ${pkgName} ==========\n`);180      spinner.start(`Publishing ${pkgName}@${newVersion} to npm\n`);181182      let opts = [];183      if (argv.debug === true) {184        opts.push('--dry-run');185      }186      if (otp != null) {187        opts.push(`--otp=${otp}`);188      }189190      opts.push(`--tag=${argv.tag}`);191192      try {193        await spawnHelper(194          'npm',195          ['publish', ...opts, '--registry=https://registry.npmjs.org'],196          {197            cwd: pkgDir,198            stdio: 'inherit',199          }200        );201        console.log('\n');202      } catch (e) {203        spinner.fail(e.toString());204        throw e;205      }206      spinner.succeed(`Successfully published ${pkgName} to npm`);207208      spinner.start('Pushing tags to npm');209      if (typeof argv.tag === 'string') {210        try {211          let opts = ['dist-tag', 'add', `${pkgName}@${newVersion}`, argv.tag];212          if (otp != null) {213            opts.push(`--otp=${otp}`);214          }215          if (argv.debug === true) {216            spinner.info(`dry-run: npm ${opts.join(' ')}`);217          } else {218            await spawnHelper('npm', opts, {219              cwd: pkgDir,220              stdio: 'inherit',221            });222          }223        } catch (e) {224          spinner.fail(e.toString());225          throw e;226        }227        spinner.succeed(228          `Successfully pushed dist-tag ${argv.tag} for ${pkgName} to npm`229        );230      }231    }232233    console.log('\n\nāœ… All done');234  }235}236237main();

Code quality findings 24

Ensure all async functions handle errors properly
info correctness async-without-catch
async function main() {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.debug === false) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const isPristine = (await execHelper('git status --porcelain')) === '';
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (currBranchName !== 'main' || isPristine === false) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (Array.isArray(argv.packages) === false) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
argv.forReal === true ? '(for real)' : '(dry run)'
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.forReal === false) {
Remove debugging statements or use a logging library
info correctness console-log
console.log(`\n========== ${pkgName} ==========\n`);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
console.log(`\n========== ${pkgName} ==========\n`);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.forReal === true) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
argv.ci === false && argv.debug === false ? await promptForOTP() : null;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.tag === 'latest') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
argv.tagVersion == null || argv.tagVersion === ''
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.tag === 'experimental' || argv.tag === 'beta') {
Remove debugging statements or use a logging library
info correctness console-log
console.log(`\n========== ${pkgName} ==========\n`);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
console.log(`\n========== ${pkgName} ==========\n`);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.debug === true) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (otp != null) {
Remove debugging statements or use a logging library
info correctness console-log
console.log('\n');
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof argv.tag === 'string') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof argv.tag === 'string') {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (otp != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (argv.debug === true) {
Remove debugging statements or use a logging library
info correctness console-log
console.log('\n\nāœ… All done');

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.