import mri from 'mri';
import { ExitError, PreEnterButInPreModeError, PreExitButNotInPreModeError, InternalError } from '@changesets/errors';
import * as logger from '@changesets/logger';
import { prefix, error, success, log, warn, info } from '@changesets/logger';
import { format } from 'util';
import { defaultWrittenConfig, read } from '@changesets/config';
import { getDependentsGraph } from '@changesets/get-dependents-graph';
import { shouldSkipPackage } from '@changesets/should-skip-package';
import { getPackages } from '@manypkg/get-packages';
import fs from 'fs-extra';
import path, { resolve } from 'path';
import pc from 'picocolors';
import { spawn, spawnSync } from 'child_process';
import * as git from '@changesets/git';
import { getChangedPackagesSinceRef, getCurrentCommitId } from '@changesets/git';
import writeChangeset from '@changesets/write';
import { edit, ExternalEditor } from '@inquirer/external-editor';
import resolveFrom from 'resolve-from';
import termSize from 'term-size';
import { prompt } from 'enquirer';
import { symbols } from 'ansi-colors';
import semverLt from 'semver/functions/lt';
import { enterPre, exitPre, readPreState } from '@changesets/pre';
import semverParse from 'semver/functions/parse';
import { detect } from 'package-manager-detector';
import spawn$1 from 'spawndamnit';
import getReleasePlan from '@changesets/get-release-plan';
import applyReleasePlan from '@changesets/apply-release-plan';
import readChangesets from '@changesets/read';
import assembleReleasePlan from '@changesets/assemble-release-plan';

function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || "default");
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}

function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : i + "";
}

function _defineProperty(e, r, t) {
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
    value: t,
    enumerable: !0,
    configurable: !0,
    writable: !0
  }) : e[r] = t, e;
}

function ownKeys(e, r) {
  var t = Object.keys(e);
  if (Object.getOwnPropertySymbols) {
    var o = Object.getOwnPropertySymbols(e);
    r && (o = o.filter(function (r) {
      return Object.getOwnPropertyDescriptor(e, r).enumerable;
    })), t.push.apply(t, o);
  }
  return t;
}
function _objectSpread2(e) {
  for (var r = 1; r < arguments.length; r++) {
    var t = null != arguments[r] ? arguments[r] : {};
    r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
      _defineProperty(e, r, t[r]);
    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
    });
  }
  return e;
}

async function getCommitFunctions(commit, cwd) {
  let commitFunctions = {};
  if (!commit) {
    return [commitFunctions, null];
  }
  let commitOpts = commit[1];
  let changesetPath = path.join(cwd, ".changeset");
  let commitPath = resolveFrom(changesetPath, commit[0]);
  let possibleCommitFunc = await import(commitPath);
  if (possibleCommitFunc.default) {
    possibleCommitFunc = possibleCommitFunc.default;

    // Check nested default again in case it's CJS with `__esModule` interop
    if (possibleCommitFunc.default) {
      possibleCommitFunc = possibleCommitFunc.default;
    }
  }
  if (typeof possibleCommitFunc.getAddMessage === "function" || typeof possibleCommitFunc.getVersionMessage === "function") {
    commitFunctions = possibleCommitFunc;
  } else {
    throw new Error("Could not resolve commit generation functions");
  }
  return [commitFunctions, commitOpts];
}

// @ts-ignore it's not worth writing a TS declaration file in this repo for a tiny module we use once like this

// those types are not exported from `enquirer` so we extract them here
// so we can make type assertions using them because `enquirer` types do no support `prefix` right now

/* Notes on using inquirer:
 * Each question needs a key, as inquirer is assembling an object behind-the-scenes.
 * At each call, the entire responses object is returned, so we need a unique
 * identifier for the name every time. This is why we are using serial IDs
 */
const serialId = function () {
  let id = 0;
  return () => id++;
}();

// this can exec tput so we make it compute lazily to avoid such side effects at init time
let limit;
const getLimit = () => limit !== null && limit !== void 0 ? limit : limit = Math.max(termSize().rows - 5, 10);
let cancelFlow = () => {
  success("Cancelled... 👋 ");
  process.exit();
};
async function askCheckboxPlus(message, choices, format) {
  const name = `CheckboxPlus-${serialId()}`;
  return prompt({
    type: "autocomplete",
    name,
    message,
    prefix,
    multiple: true,
    choices,
    format,
    limit: getLimit(),
    onCancel: cancelFlow,
    symbols: {
      indicator: symbols.radioOff,
      checked: symbols.radioOn
    },
    indicator(state, choice) {
      return choice.enabled ? state.symbols.checked : state.symbols.indicator;
    }
  }).then(responses => responses[name]).catch(err => {
    error(err);
  });
}
async function askQuestion(message) {
  const name = `Question-${serialId()}`;
  return prompt([{
    type: "input",
    message,
    name,
    prefix,
    onCancel: cancelFlow
  }]).then(responses => responses[name]).catch(err => {
    error(err);
  });
}
function askQuestionWithEditor(message) {
  const response = edit(message, {
    postfix: ".md"
  });
  return response.replace(/^#.*\n?/gm, "").replace(/\n+$/g, "").trim();
}
async function askConfirm(message) {
  const name = `Confirm-${serialId()}`;
  return prompt([{
    message,
    name,
    prefix,
    type: "confirm",
    initial: true,
    onCancel: cancelFlow
  }]).then(responses => responses[name]).catch(err => {
    error(err);
  });
}
async function askList(message, choices) {
  const name = `List-${serialId()}`;
  return prompt([{
    choices,
    message,
    name,
    prefix,
    type: "select",
    onCancel: cancelFlow
  }]).then(responses => responses[name]).catch(err => {
    error(err);
  });
}

async function getVersionableChangedPackages(config, {
  cwd,
  ref
}) {
  const changedPackages = await getChangedPackagesSinceRef({
    ref: ref !== null && ref !== void 0 ? ref : config.baseBranch,
    changedFilePatterns: config.changedFilePatterns,
    cwd
  });
  return changedPackages.filter(pkg => !shouldSkipPackage(pkg, {
    ignore: config.ignore,
    allowPrivatePackages: config.privatePackages.version
  }));
}

const {
  green,
  yellow,
  red,
  bold,
  blue,
  cyan,
  gray
} = pc;
async function confirmMajorRelease(pkgJSON) {
  if (semverLt(pkgJSON.version, "1.0.0")) {
    // prettier-ignore
    log(yellow(`WARNING: Releasing a major version for ${green(pkgJSON.name)} will be its ${red('first major release')}.`));
    log(yellow(`If you are unsure if this is correct, contact the package's maintainers ${red("before committing this changeset")}.`));
    let shouldReleaseFirstMajor = await askConfirm(bold(`Are you sure you want to release the ${red("first major version")} of ${pkgJSON.name}?`));
    return shouldReleaseFirstMajor;
  }
  return true;
}
async function getPackagesToRelease(changedPackages, allPackages) {
  function askInitialReleaseQuestion(defaultChoiceList) {
    return askCheckboxPlus(
    // TODO: Make this wording better
    // TODO: take objects and be fancy with matching
    `Which packages would you like to include?`, defaultChoiceList, x => {
      // this removes changed packages and unchanged packages from the list
      // of packages shown after selection
      if (Array.isArray(x)) {
        return x.filter(x => x !== "changed packages" && x !== "unchanged packages").map(x => cyan(x)).join(", ");
      }
      return x;
    });
  }
  if (allPackages.length > 1) {
    const unchangedPackagesNames = allPackages.map(({
      packageJson
    }) => packageJson.name).filter(name => !changedPackages.includes(name));
    const defaultChoiceList = [{
      name: "changed packages",
      choices: changedPackages
    }, {
      name: "unchanged packages",
      choices: unchangedPackagesNames
    }].filter(({
      choices
    }) => choices.length !== 0);
    let packagesToRelease = await askInitialReleaseQuestion(defaultChoiceList);
    if (packagesToRelease.length === 0) {
      do {
        error("You must select at least one package to release");
        error("(Press space to mark/unmark one option. Press enter to confirm.)");
        packagesToRelease = await askInitialReleaseQuestion(defaultChoiceList);
      } while (packagesToRelease.length === 0);
    }
    return packagesToRelease.filter(pkgName => pkgName !== "changed packages" && pkgName !== "unchanged packages");
  }
  return [allPackages[0].packageJson.name];
}
function getPkgJsonsByName(packages) {
  return new Map(packages.map(({
    packageJson
  }) => [packageJson.name, packageJson]));
}
function formatPkgNameAndVersion(pkgName, version) {
  return `${bold(pkgName)}@${bold(version)}`;
}
async function createChangeset(changedPackages, allPackages, messageFromCli) {
  const releases = [];
  if (allPackages.length > 1) {
    const packagesToRelease = await getPackagesToRelease(changedPackages, allPackages);
    let pkgJsonsByName = getPkgJsonsByName(allPackages);
    let pkgsLeftToGetBumpTypeFor = new Set(packagesToRelease);
    let pkgsThatShouldBeMajorBumped = (await askCheckboxPlus(bold(`Which packages should have a ${red("major")} bump?`), [{
      name: "all packages",
      choices: packagesToRelease.map(pkgName => {
        return {
          name: pkgName,
          message: formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version)
        };
      })
    }], x => {
      // this removes changed packages and unchanged packages from the list
      // of packages shown after selection
      if (Array.isArray(x)) {
        return x.filter(x => x !== "all packages").map(x => cyan(x)).join(", ");
      }
      return x;
    })).filter(x => x !== "all packages");
    for (const pkgName of pkgsThatShouldBeMajorBumped) {
      // for packages that are under v1, we want to make sure major releases are intended,
      // as some repo-wide sweeping changes have mistakenly release first majors
      // of packages.
      let pkgJson = pkgJsonsByName.get(pkgName);
      let shouldReleaseFirstMajor = await confirmMajorRelease(pkgJson);
      if (shouldReleaseFirstMajor) {
        pkgsLeftToGetBumpTypeFor.delete(pkgName);
        releases.push({
          name: pkgName,
          type: "major"
        });
      }
    }
    if (pkgsLeftToGetBumpTypeFor.size !== 0) {
      let pkgsThatShouldBeMinorBumped = (await askCheckboxPlus(bold(`Which packages should have a ${green("minor")} bump?`), [{
        name: "all packages",
        choices: [...pkgsLeftToGetBumpTypeFor].map(pkgName => {
          return {
            name: pkgName,
            message: formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version)
          };
        })
      }], x => {
        // this removes changed packages and unchanged packages from the list
        // of packages shown after selection
        if (Array.isArray(x)) {
          return x.filter(x => x !== "all packages").map(x => cyan(x)).join(", ");
        }
        return x;
      })).filter(x => x !== "all packages");
      for (const pkgName of pkgsThatShouldBeMinorBumped) {
        pkgsLeftToGetBumpTypeFor.delete(pkgName);
        releases.push({
          name: pkgName,
          type: "minor"
        });
      }
    }
    if (pkgsLeftToGetBumpTypeFor.size !== 0) {
      log(`The following packages will be ${blue("patch")} bumped:`);
      pkgsLeftToGetBumpTypeFor.forEach(pkgName => {
        log(formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version));
      });
      for (const pkgName of pkgsLeftToGetBumpTypeFor) {
        releases.push({
          name: pkgName,
          type: "patch"
        });
      }
    }
  } else {
    let pkg = allPackages[0];
    let type = await askList(`What kind of change is this for ${green(pkg.packageJson.name)}? (current version is ${pkg.packageJson.version})`, ["patch", "minor", "major"]);
    if (type === "major") {
      let shouldReleaseAsMajor = await confirmMajorRelease(pkg.packageJson);
      if (!shouldReleaseAsMajor) {
        throw new ExitError(1);
      }
    }
    releases.push({
      name: pkg.packageJson.name,
      type
    });
  }
  if (messageFromCli !== undefined) {
    return {
      confirmed: false,
      summary: messageFromCli,
      releases
    };
  }
  log("Please enter a summary for this change (this will be in the changelogs).");
  log(gray("  (submit empty line to open external editor)"));
  let summary = await askQuestion("Summary");
  if (summary.length === 0) {
    try {
      summary = askQuestionWithEditor("\n\n# Please enter a summary for your changes.\n# An empty message aborts the editor.");
      if (summary.length > 0) {
        return {
          confirmed: true,
          summary,
          releases
        };
      }
    } catch (err) {
      log("An error happened using external editor. Please type your summary here:");
    }
    summary = await askQuestion("Summary");
    while (summary.length === 0) {
      summary = await askQuestion("A summary is required! Please enter a summary");
    }
  }
  return {
    confirmed: false,
    summary,
    releases
  };
}

function printConfirmationMessage(changeset, repoHasMultiplePackages) {
  function getReleasesOfType(type) {
    return changeset.releases.filter(release => release.type === type).map(release => release.name);
  }
  log("\n=== Summary of changesets ===");
  const majorReleases = getReleasesOfType("major");
  const minorReleases = getReleasesOfType("minor");
  const patchReleases = getReleasesOfType("patch");
  if (majorReleases.length > 0) log(`${pc.bold(pc.green("major"))}:  ${majorReleases.join(", ")}`);
  if (minorReleases.length > 0) log(`${pc.bold(pc.green("minor"))}:  ${minorReleases.join(", ")}`);
  if (patchReleases.length > 0) log(`${pc.bold(pc.green("patch"))}:  ${patchReleases.join(", ")}`);
  log("");
  if (repoHasMultiplePackages) {
    const message = "Note: All dependents of these packages that will be incompatible with the new version will be " + pc.redBright("patch bumped") + " when this changeset is applied.";
    log(message + "\n");
  }
}

async function add(cwd, {
  empty,
  open,
  since,
  message
}, config) {
  const packages = await getPackages(cwd);
  if (packages.packages.length === 0) {
    error(`No packages found. You might have ${packages.tool} workspaces configured but no packages yet?`);
    throw new ExitError(1);
  }
  const versionablePackages = packages.packages.filter(pkg => !shouldSkipPackage(pkg, {
    ignore: config.ignore,
    allowPrivatePackages: config.privatePackages.version
  }));
  if (versionablePackages.length === 0) {
    error("No versionable packages found");
    error('- Ensure the packages to version are not in the "ignore" config');
    error('- Ensure that relevant package.json files have the "version" field');
    throw new ExitError(1);
  }
  const changesetBase = path.resolve(cwd, ".changeset");
  let newChangeset;
  if (empty) {
    newChangeset = {
      confirmed: true,
      releases: [],
      summary: message !== null && message !== void 0 ? message : ""
    };
  } else {
    let changedPackagesNames = [];
    try {
      changedPackagesNames = (await getVersionableChangedPackages(config, {
        cwd,
        ref: since
      })).map(pkg => pkg.packageJson.name);
    } catch (e) {
      // NOTE: Getting the changed packages is best effort as it's only being used for easier selection
      // in the CLI. So if any error happens while we try to do so, we only log a warning and continue
      const branch = since !== null && since !== void 0 ? since : config.baseBranch;
      warn(`Failed to find changed packages from the "${branch}" ${since ? "ref" : "base branch"} due to error below`);
      warn(e);
    }
    newChangeset = await createChangeset(changedPackagesNames, versionablePackages, message);
    printConfirmationMessage(newChangeset, versionablePackages.length > 1);
    if (!newChangeset.confirmed) {
      newChangeset = _objectSpread2(_objectSpread2({}, newChangeset), {}, {
        confirmed: await askConfirm("Is this your desired changeset?")
      });
    }
  }
  if (newChangeset.confirmed) {
    const changesetID = await writeChangeset(newChangeset, cwd, config);
    const [{
      getAddMessage
    }, commitOpts] = await getCommitFunctions(config.commit, cwd);
    if (getAddMessage) {
      await git.add(path.resolve(changesetBase, `${changesetID}.md`), cwd);
      await git.commit(await getAddMessage(newChangeset, commitOpts), cwd);
      log(pc.green(`${empty ? "Empty " : ""}Changeset added and committed`));
    } else {
      log(pc.green(`${empty ? "Empty " : ""}Changeset added! - you can now commit it\n`));
    }
    let hasMajorChange = [...newChangeset.releases].find(c => c.type === "major");
    if (hasMajorChange) {
      warn("This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:");
      warn("WHAT the breaking change is");
      warn("WHY the change was made");
      warn("HOW a consumer should update their code");
    } else {
      log(pc.green("If you want to modify or expand on the changeset summary, you can find it here"));
    }
    const changesetPath = path.resolve(changesetBase, `${changesetID}.md`);
    info(pc.blue(changesetPath));
    if (open) {
      // this is really a hack to reuse the logic embedded in `external-editor` related to determining the editor
      const externalEditor = new ExternalEditor();
      externalEditor.cleanup();
      spawn(externalEditor.editor.bin, externalEditor.editor.args.concat([changesetPath]), {
        detached: true,
        stdio: "inherit"
      });
    }
  }
}

const pkgPath = path.dirname(require.resolve("@changesets/cli/package.json"));

// Modify base branch to "main" without changing defaultWrittenConfig since it also serves as a fallback
// for config files that don't specify a base branch. Changing that to main would be a breaking change.
const defaultConfig = `${JSON.stringify(_objectSpread2(_objectSpread2({}, defaultWrittenConfig), {}, {
  baseBranch: "main"
}), null, 2)}\n`;
async function init(cwd) {
  const changesetBase = path.resolve(cwd, ".changeset");
  if (fs.existsSync(changesetBase)) {
    if (!fs.existsSync(path.join(changesetBase, "config.json"))) {
      if (fs.existsSync(path.join(changesetBase, "config.js"))) {
        error("It looks like you're using the version 1 `.changeset/config.js` file");
        error("The format of the config object has significantly changed in v2 as well");
        error(" - we thoroughly recommend looking at the changelog for this package for what has changed");
        error("Changesets will write the defaults for the new config, remember to transfer your options into the new config at `.changeset/config.json`");
      } else {
        error("It looks like you don't have a config file");
        info("The default config file will be written at `.changeset/config.json`");
      }
      await fs.writeFile(path.resolve(changesetBase, "config.json"), defaultConfig);
    } else {
      warn("It looks like you already have changesets initialized. You should be able to run changeset commands no problems.");
    }
  } else {
    await fs.copy(path.resolve(pkgPath, "./default-files"), changesetBase);
    await fs.writeFile(path.resolve(changesetBase, "config.json"), defaultConfig);
    log(`Thanks for choosing ${pc.green("changesets")} to help manage your versioning and publishing.\n`);
    log("You should be set up to start using changesets now!\n");
    info("We have added a `.changeset` folder, and a couple of files to help you out:");
    info(`- ${pc.blue(".changeset/README.md")} contains information about using changesets`);
    info(`- ${pc.blue(".changeset/config.json")} is our default config`);
  }
}

async function pre(rootDir, options) {
  if (options.command === "enter") {
    try {
      await enterPre(rootDir, options.tag);
      logger.success(`Entered pre mode with tag ${pc.cyan(options.tag)}`);
      logger.info("Run `changeset version` to version packages with prerelease versions");
    } catch (err) {
      if (err instanceof PreEnterButInPreModeError) {
        logger.error("`changeset pre enter` cannot be run when in pre mode");
        logger.info("If you're trying to exit pre mode, run `changeset pre exit`");
        throw new ExitError(1);
      }
      throw err;
    }
  } else {
    try {
      await exitPre(rootDir);
      logger.success(`Exited pre mode`);
      logger.info("Run `changeset version` to version packages with normal versions");
    } catch (err) {
      if (err instanceof PreExitButNotInPreModeError) {
        logger.error("`changeset pre exit` can only be run when in pre mode");
        logger.info("If you're trying to enter pre mode, run `changeset pre enter`");
        throw new ExitError(1);
      }
      throw err;
    }
  }
}

function withResolvers() {
  const rv = {};
  rv.promise = new Promise((resolve, reject) => {
    rv.resolve = resolve;
    rv.reject = reject;
  });
  return rv;
}
function promiseTry(fn) {
  return new Promise(resolve => resolve(fn()));
}
function createPromiseQueue(concurrency) {
  const jobs = [];
  let active = 0;
  function run() {
    if (active >= concurrency) {
      return;
    }
    const job = jobs.shift();
    if (!job) {
      return;
    }
    active++;
    promiseTry(job.fn).then(result => {
      active--;
      job.resolve(result);
      run();
    }, error => {
      active--;
      job.reject(error);
      run();
    });
  }
  return {
    add: fn => {
      const {
        promise,
        resolve,
        reject
      } = withResolvers();
      jobs.push({
        fn,
        resolve,
        reject
      });
      run();
      return promise;
    },
    setConcurrency: newConcurrency => {
      concurrency = newConcurrency;
      run();
    }
  };
}

const getLastJsonObjectFromString = str => {
  str = str.replace(/[^}]*$/, "");
  while (str) {
    str = str.replace(/[^{]*/, "");
    try {
      return JSON.parse(str);
    } catch (err) {
      // move past the potentially leading `{` so the regexp in the loop can try to match for the next `{`
      str = str.slice(1);
    }
  }
  return null;
};

const NPM_REQUEST_CONCURRENCY_LIMIT = 40;
const NPM_PUBLISH_CONCURRENCY_LIMIT = 10;
const npmRequestQueue = createPromiseQueue(NPM_REQUEST_CONCURRENCY_LIMIT);
const npmPublishQueue = createPromiseQueue(NPM_PUBLISH_CONCURRENCY_LIMIT);
function jsonParse(input) {
  try {
    return JSON.parse(input);
  } catch (err) {
    if (err instanceof SyntaxError) {
      console.error("error parsing json:", input);
    }
    throw err;
  }
}
const isCustomRegistry = registry => {
  registry = normalizeRegistry(registry);
  return !!registry && registry !== "https://registry.npmjs.org" && registry !== "https://registry.yarnpkg.com";
};
function normalizeRegistry(registry) {
  return registry && registry.replace(/\/+$/, "");
}
function getCorrectRegistry(packageJson) {
  var _packageJson$publishC;
  const packageName = packageJson === null || packageJson === void 0 ? void 0 : packageJson.name;
  if (packageName !== null && packageName !== void 0 && packageName.startsWith("@")) {
    var _publishConfig;
    const scope = packageName.split("/")[0];
    const scopedRegistry = normalizeRegistry(((_publishConfig = packageJson.publishConfig) === null || _publishConfig === void 0 ? void 0 : _publishConfig[`${scope}:registry`]) || process.env[`npm_config_${scope}:registry`]);
    if (scopedRegistry) {
      return {
        scope,
        registry: scopedRegistry
      };
    }
  }
  const registry = normalizeRegistry((packageJson === null || packageJson === void 0 || (_packageJson$publishC = packageJson.publishConfig) === null || _packageJson$publishC === void 0 ? void 0 : _packageJson$publishC.registry) || process.env.npm_config_registry);
  return {
    scope: undefined,
    registry: !registry || registry === "https://registry.yarnpkg.com" ? "https://registry.npmjs.org" : registry
  };
}
async function getPublishTool(cwd) {
  const pm = await detect({
    cwd
  });
  if (!pm || pm.name !== "pnpm") return {
    name: "npm"
  };
  try {
    let result = await spawn$1("pnpm", ["--version"], {
      cwd
    });
    let version = result.stdout.toString().trim();
    let parsed = semverParse(version);
    return {
      name: "pnpm",
      shouldAddNoGitChecks: (parsed === null || parsed === void 0 ? void 0 : parsed.major) === undefined ? false : parsed.major >= 5
    };
  } catch (e) {
    return {
      name: "pnpm",
      shouldAddNoGitChecks: false
    };
  }
}
async function getTokenIsRequired() {
  const {
    scope,
    registry
  } = getCorrectRegistry();
  // Due to a super annoying issue in yarn, we have to manually override this env variable
  // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
  const envOverride = {
    [scope ? `npm_config_${scope}:registry` : "npm_config_registry"]: registry
  };
  let result = await spawn$1("npm", ["profile", "get", "--json"], {
    env: Object.assign({}, process.env, envOverride)
  });
  if (result.code !== 0) {
    error("error while checking if token is required", result.stderr.toString().trim() || result.stdout.toString().trim());
    return false;
  }
  let json = jsonParse(result.stdout.toString());
  if (json.error || !json.tfa || !json.tfa.mode) {
    return false;
  }
  return json.tfa.mode === "auth-and-writes";
}
function getPackageInfo(packageJson) {
  return npmRequestQueue.add(async () => {
    info(`npm info ${packageJson.name}`);
    const {
      scope,
      registry
    } = getCorrectRegistry(packageJson);

    // Due to a couple of issues with yarnpkg, we also want to override the npm registry when doing
    // npm info.
    // Issues: We sometimes get back cached responses, i.e old data about packages which causes
    // `publish` to behave incorrectly. It can also cause issues when publishing private packages
    // as they will always give a 404, which will tell `publish` to always try to publish.
    // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
    let result = await spawn$1("npm", ["info", packageJson.name, `--${scope ? `${scope}:` : ""}registry=${registry}`, "--json"]);

    // Github package registry returns empty string when calling npm info
    // for a non-existent package instead of a E404
    if (result.stdout.toString() === "") {
      return {
        error: {
          code: "E404"
        }
      };
    }
    return jsonParse(result.stdout.toString());
  });
}
async function infoAllow404(packageJson) {
  var _pkgInfo$error;
  let pkgInfo = await getPackageInfo(packageJson);
  if (((_pkgInfo$error = pkgInfo.error) === null || _pkgInfo$error === void 0 ? void 0 : _pkgInfo$error.code) === "E404") {
    warn(`Received 404 for npm info ${pc.cyan(`"${packageJson.name}"`)}`);
    return {
      published: false,
      pkgInfo: {}
    };
  }
  if (pkgInfo.error) {
    error(`Received an unknown error code: ${pkgInfo.error.code} for npm info ${pc.cyan(`"${packageJson.name}"`)}`);
    error(pkgInfo.error.summary);
    if (pkgInfo.error.detail) error(pkgInfo.error.detail);
    throw new ExitError(1);
  }
  return {
    published: true,
    pkgInfo
  };
}

// we have this so that we can do try a publish again after a publish without
// the call being wrapped in the npm request limit and causing the publishes to potentially never run
async function internalPublish(packageJson, opts, twoFactorState) {
  const publishTool = await getPublishTool(opts.cwd);
  const publishFlags = opts.access ? ["--access", opts.access] : [];
  publishFlags.push("--tag", opts.tag);
  if (publishTool.name === "pnpm" && publishTool.shouldAddNoGitChecks) {
    publishFlags.push("--no-git-checks");
  }
  const {
    scope,
    registry
  } = getCorrectRegistry(packageJson);

  // Due to a super annoying issue in yarn, we have to manually override this env variable
  // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
  const envOverride = {
    [scope ? `npm_config_${scope}:registry` : "npm_config_registry"]: registry
  };
  if (requiresDelegatedAuth(twoFactorState)) {
    const result = publishTool.name === "pnpm" ? spawnSync("pnpm", ["publish", ...publishFlags], {
      env: Object.assign({}, process.env, envOverride),
      cwd: opts.cwd,
      stdio: "inherit"
    }) : spawnSync(publishTool.name, ["publish", opts.publishDir, ...publishFlags], {
      env: Object.assign({}, process.env, envOverride),
      stdio: "inherit"
    });
    if (result.status === 0) {
      twoFactorState.allowConcurrency = true;
      // bump for remaining packages
      npmPublishQueue.setConcurrency(NPM_PUBLISH_CONCURRENCY_LIMIT);
      return {
        published: true
      };
    }
    return {
      published: false
    };
  }

  // in the delegated mode we don't need the json output
  // as we won't be handling the auth errors
  publishFlags.push("--json");
  if (twoFactorState.token) {
    publishFlags.push("--otp", twoFactorState.token);
  }
  let {
    code,
    stdout,
    stderr
  } = publishTool.name === "pnpm" ? await spawn$1("pnpm", ["publish", ...publishFlags], {
    env: Object.assign({}, process.env, envOverride),
    cwd: opts.cwd
  }) : await spawn$1(publishTool.name, ["publish", opts.publishDir, ...publishFlags], {
    env: Object.assign({}, process.env, envOverride)
  });
  if (code !== 0) {
    // NPM's --json output is included alongside the `prepublish` and `postpublish` output in terminal
    // We want to handle this as best we can but it has some struggles:
    // - output of those lifecycle scripts can contain JSON
    // - npm7 has switched to printing `--json` errors to stderr (https://github.com/npm/cli/commit/1dbf0f9bb26ba70f4c6d0a807701d7652c31d7d4)
    // Note that the `--json` output is always printed at the end so this should work
    let json = getLastJsonObjectFromString(stderr.toString()) || getLastJsonObjectFromString(stdout.toString());
    if (json !== null && json !== void 0 && json.error) {
      var _json$error$detail;
      // The first case is no 2fa provided, the second is when the 2fa is wrong (timeout or wrong words)
      if ((json.error.code === "EOTP" || json.error.code === "E401" && (_json$error$detail = json.error.detail) !== null && _json$error$detail !== void 0 && _json$error$detail.includes("--otp=<code>")) && process.stdin.isTTY) {
        // the current otp code must be invalid since it errored
        twoFactorState.token = undefined;
        // just in case this isn't already true
        twoFactorState.isRequired = true;
        twoFactorState.allowConcurrency = false;
        npmPublishQueue.setConcurrency(1);
        return {
          published: false,
          // given we have just adjusted the concurrency, we need to handle the retries in the layer that requeues the publish
          // calling internalPublish again would allow concurrent failures to run again concurrently
          // but only one retried publish should get delegated to the npm cli and other ones should "await" its successful result before being retried
          allowRetry: true
        };
      }
      error(`an error occurred while publishing ${packageJson.name}: ${json.error.code}`, json.error.summary, json.error.detail ? "\n" + json.error.detail : "");
    }
    error(stderr.toString() || stdout.toString());
    return {
      published: false
    };
  }
  return {
    published: true
  };
}
function publish$1(packageJson, opts, twoFactorState) {
  return npmRequestQueue.add(async () => {
    let result;
    do {
      result = await npmPublishQueue.add(() => internalPublish(packageJson, opts, twoFactorState));
    } while (result.allowRetry);
    return {
      published: result.published
    };
  });
}

function getReleaseTag(pkgInfo, preState, tag) {
  if (tag) return tag;
  if (preState !== undefined && pkgInfo.publishedState !== "only-pre") {
    return preState.tag;
  }
  return "latest";
}
const getTwoFactorState = async ({
  otp,
  publicPackages
}) => {
  if (otp) {
    return {
      token: otp,
      isRequired: true
    };
  }
  if (!process.stdin.isTTY || publicPackages.some(pkg => isCustomRegistry(getCorrectRegistry(pkg.packageJson).registry)) || isCustomRegistry(process.env.npm_config_registry)) {
    return {
      token: undefined,
      isRequired: false
    };
  }
  return {
    token: undefined,
    isRequired: await getTokenIsRequired()
  };
};
const requiresDelegatedAuth = twoFactorState => {
  return process.stdin.isTTY && !twoFactorState.token && !twoFactorState.allowConcurrency && twoFactorState.isRequired;
};
async function publishPackages({
  packages,
  access,
  otp,
  preState,
  tag
}) {
  const packagesByName = new Map(packages.map(x => [x.packageJson.name, x]));
  const publicPackages = packages.filter(pkg => !pkg.packageJson.private);
  const unpublishedPackagesInfo = await getUnpublishedPackages(publicPackages, preState);
  if (unpublishedPackagesInfo.length === 0) {
    return [];
  }
  const twoFactorState = await getTwoFactorState({
    otp,
    publicPackages
  });
  if (requiresDelegatedAuth(twoFactorState)) {
    npmPublishQueue.setConcurrency(1);
  }
  return Promise.all(unpublishedPackagesInfo.map(pkgInfo => {
    let pkg = packagesByName.get(pkgInfo.name);
    return publishAPackage(pkg, access, twoFactorState, getReleaseTag(pkgInfo, preState, tag));
  }));
}
async function publishAPackage(pkg, access, twoFactorState, tag) {
  const {
    name,
    version,
    publishConfig
  } = pkg.packageJson;
  info(`Publishing ${pc.cyan(`"${name}"`)} at ${pc.green(`"${version}"`)}`);
  const publishConfirmation = await publish$1(pkg.packageJson, {
    cwd: pkg.dir,
    publishDir: publishConfig !== null && publishConfig !== void 0 && publishConfig.directory ? resolve(pkg.dir, publishConfig.directory) : pkg.dir,
    access: (publishConfig === null || publishConfig === void 0 ? void 0 : publishConfig.access) || access,
    tag
  }, twoFactorState);
  return {
    name,
    newVersion: version,
    published: publishConfirmation.published
  };
}
async function getUnpublishedPackages(packages, preState) {
  const results = await Promise.all(packages.map(async ({
    packageJson
  }) => {
    const response = await infoAllow404(packageJson);
    let publishedState = "never";
    if (response.published) {
      publishedState = "published";
      if (preState !== undefined) {
        if (response.pkgInfo.versions && response.pkgInfo.versions.every(version => semverParse(version).prerelease[0] === preState.tag)) {
          publishedState = "only-pre";
        }
      }
    }
    return {
      name: packageJson.name,
      localVersion: packageJson.version,
      publishedState,
      publishedVersions: response.pkgInfo.versions || []
    };
  }));
  const packagesToPublish = [];
  for (const pkgInfo of results) {
    const {
      name,
      publishedState,
      localVersion,
      publishedVersions
    } = pkgInfo;
    if (!publishedVersions.includes(localVersion)) {
      packagesToPublish.push(pkgInfo);
      info(`${name} is being published because our local version (${localVersion}) has not been published on npm`);
      if (preState !== undefined && publishedState === "only-pre") {
        info(`${name} is being published to ${pc.cyan("latest")} rather than ${pc.cyan(preState.tag)} because there has not been a regular release of it yet`);
      }
    } else {
      // If the local version is behind npm, something is wrong, we warn here, and by not getting published later, it will fail
      warn(`${name} is not being published because version ${localVersion} is already published on npm`);
    }
  }
  return packagesToPublish;
}

async function getUntaggedPackages(packages, cwd, tool) {
  const packageWithTags = await Promise.all(packages.map(async pkg => {
    const tagName = tool === "root" ? `v${pkg.packageJson.version}` : `${pkg.packageJson.name}@${pkg.packageJson.version}`;
    const isMissingTag = !((await git.tagExists(tagName, cwd)) || (await git.remoteTagExists(tagName)));
    return {
      pkg,
      isMissingTag
    };
  }));
  const untagged = [];
  for (const packageWithTag of packageWithTags) {
    if (packageWithTag.isMissingTag) {
      untagged.push({
        name: packageWithTag.pkg.packageJson.name,
        newVersion: packageWithTag.pkg.packageJson.version,
        published: false
      });
    }
  }
  return untagged;
}

function logReleases(pkgs) {
  const mappedPkgs = pkgs.map(p => `${p.name}@${p.newVersion}`).join("\n");
  log(mappedPkgs);
}
let importantSeparator$1 = pc.red("===============================IMPORTANT!===============================");
let importantEnd$1 = pc.red("----------------------------------------------------------------------");
function showNonLatestTagWarning(tag, preState) {
  warn(importantSeparator$1);
  if (preState) {
    warn(`You are in prerelease mode so packages will be published to the ${pc.cyan(preState.tag)}
        dist tag except for packages that have not had normal releases which will be published to ${pc.cyan("latest")}`);
  } else if (tag !== "latest") {
    warn(`Packages will be released under the ${tag} tag`);
  }
  warn(importantEnd$1);
}
async function publish(cwd, {
  otp,
  tag,
  gitTag = true
}, config) {
  const releaseTag = tag && tag.length > 0 ? tag : undefined;
  let preState = await readPreState(cwd);
  if (releaseTag && preState && preState.mode === "pre") {
    error("Releasing under custom tag is not allowed in pre mode");
    log("To resolve this exit the pre mode by running `changeset pre exit`");
    throw new ExitError(1);
  }
  if (releaseTag || preState) {
    showNonLatestTagWarning(tag, preState);
  }
  const {
    packages,
    tool
  } = await getPackages(cwd);
  const tagPrivatePackages = config.privatePackages && config.privatePackages.tag;
  const publishedPackages = await publishPackages({
    packages,
    // if not public, we won't pass the access, and it works as normal
    access: config.access,
    otp,
    preState,
    tag: releaseTag
  });
  const privatePackages = packages.filter(pkg => pkg.packageJson.private && pkg.packageJson.version);
  const untaggedPrivatePackageReleases = tagPrivatePackages ? await getUntaggedPackages(privatePackages, cwd, tool) : [];
  if (publishedPackages.length === 0 && untaggedPrivatePackageReleases.length === 0) {
    warn("No unpublished projects to publish");
  }
  const successfulNpmPublishes = publishedPackages.filter(p => p.published);
  const unsuccessfulNpmPublishes = publishedPackages.filter(p => !p.published);
  if (successfulNpmPublishes.length > 0) {
    success("packages published successfully:");
    logReleases(successfulNpmPublishes);
    if (gitTag) {
      // We create the tags after the push above so that we know that HEAD won't change and that pushing
      // won't suffer from a race condition if another merge happens in the mean time (pushing tags won't
      // fail if we are behind the base branch).
      log(`Creating git tag${successfulNpmPublishes.length > 1 ? "s" : ""}...`);
      await tagPublish(tool, successfulNpmPublishes, cwd);
    }
  }
  if (untaggedPrivatePackageReleases.length > 0) {
    success("found untagged projects:");
    logReleases(untaggedPrivatePackageReleases);
    await tagPublish(tool, untaggedPrivatePackageReleases, cwd);
  }
  if (unsuccessfulNpmPublishes.length > 0) {
    error("packages failed to publish:");
    logReleases(unsuccessfulNpmPublishes);
    throw new ExitError(1);
  }
}
async function tagPublish(tool, packageReleases, cwd) {
  if (tool !== "root") {
    for (const pkg of packageReleases) {
      const tag = `${pkg.name}@${pkg.newVersion}`;
      log("New tag: ", tag);
      await git.tag(tag, cwd);
    }
  } else {
    const tag = `v${packageReleases[0].newVersion}`;
    log("New tag: ", tag);
    await git.tag(tag, cwd);
  }
}

async function status(cwd, {
  sinceMaster,
  since,
  verbose,
  output
}, config) {
  if (sinceMaster) {
    warn("--sinceMaster is deprecated and will be removed in a future major version");
    warn("Use --since=master instead");
  }
  const sinceBranch = since === undefined ? sinceMaster ? "master" : undefined : since;
  const releasePlan = await getReleasePlan(cwd, sinceBranch, config);
  const {
    changesets,
    releases
  } = releasePlan;
  const changedPackages = await getVersionableChangedPackages(config, {
    cwd,
    ref: sinceBranch
  });
  if (changedPackages.length > 0 && changesets.length === 0) {
    error("Some packages have been changed but no changesets were found. Run `changeset add` to resolve this error.");
    error("If this change doesn't need a release, run `changeset add --empty`.");
    process.exit(1);
  }
  if (output) {
    await fs.writeFile(path.resolve(cwd, output), JSON.stringify(releasePlan, undefined, 2));
    return;
  }
  const print = verbose ? verbosePrint : SimplePrint;
  print("patch", releases);
  log("---");
  print("minor", releases);
  log("---");
  print("major", releases);
  return releasePlan;
}
function SimplePrint(type, releases) {
  const packages = releases.filter(r => r.type === type);
  if (packages.length) {
    info(`Packages to be bumped at ${pc.green(type)}:\n`);
    const pkgs = packages.map(({
      name
    }) => `- ${name}`).join("\n");
    log(pc.green(pkgs));
  } else {
    info(`${pc.green("NO")} packages to be bumped at ${pc.green(type)}`);
  }
}
function verbosePrint(type, releases) {
  const packages = releases.filter(r => r.type === type);
  if (packages.length) {
    info(`Packages to be bumped at ${pc.green(type)}`);
    for (const {
      name,
      newVersion: version,
      changesets
    } of packages) {
      log(`- ${pc.green(name)} ${pc.cyan(version)}`);
      for (const c of changesets) {
        log(`  - ${pc.blue(`.changeset/${c}.md`)}`);
      }
    }
  } else {
    info(`Running release would release ${pc.red("NO")} packages as a ${pc.green(type)}`);
  }
}

async function tag(cwd, config) {
  const {
    packages,
    tool
  } = await getPackages(cwd);
  const allExistingTags = await git.getAllTags(cwd);
  const taggablePackages = packages.filter(pkg => !shouldSkipPackage(pkg, {
    ignore: config.ignore,
    allowPrivatePackages: config.privatePackages.tag
  }));
  for (const {
    name,
    newVersion
  } of await getUntaggedPackages(taggablePackages, cwd, tool)) {
    const tag = tool !== "root" ? `${name}@${newVersion}` : `v${newVersion}`;
    if (allExistingTags.has(tag)) {
      log("Skipping tag (already exists): ", tag);
    } else {
      log("New tag: ", tag);
      await git.tag(tag, cwd);
    }
  }
}

// This helper is designed to operate on the .changeset
// folder, and tidy up the subfolders
// THIS SHOULD BE REMOVED WHEN SUPPORT FOR CHANGESETS FROM V1 IS DROPPED

const removeEmptyFolders = async folderPath => {
  const dirContents = fs.readdirSync(folderPath);
  return Promise.all(dirContents.map(async contentPath => {
    const singleChangesetPath = path.resolve(folderPath, contentPath);
    try {
      if ((await fs.readdir(singleChangesetPath)).length < 1) {
        await fs.rmdir(singleChangesetPath);
      }
    } catch (err) {
      if (err.code !== "ENOTDIR") {
        throw err;
      }
    }
  }));
};

let importantSeparator = pc.red("===============================IMPORTANT!===============================");
let importantEnd = pc.red("----------------------------------------------------------------------");
async function version(cwd, options, config) {
  var _config$snapshot$prer;
  const releaseConfig = _objectSpread2(_objectSpread2({}, config), {}, {
    // Disable committing when in snapshot mode
    commit: options.snapshot ? false : config.commit
  });
  const [changesets, preState] = await Promise.all([readChangesets(cwd), readPreState(cwd), removeEmptyFolders(path.resolve(cwd, ".changeset"))]);
  if ((preState === null || preState === void 0 ? void 0 : preState.mode) === "pre") {
    warn(importantSeparator);
    if (options.snapshot !== undefined) {
      error("Snapshot release is not allowed in pre mode");
      log("To resolve this exit the pre mode by running `changeset pre exit`");
      throw new ExitError(1);
    } else {
      warn("You are in prerelease mode");
      warn("If you meant to do a normal release you should revert these changes and run `changeset pre exit`");
      warn("You can then run `changeset version` again to do a normal release");
    }
    warn(importantEnd);
  }
  if (changesets.length === 0 && (preState === undefined || preState.mode !== "exit")) {
    warn("No unreleased changesets found, exiting.");
    return;
  }
  let packages = await getPackages(cwd);
  let releasePlan = assembleReleasePlan(changesets, packages, releaseConfig, preState, options.snapshot ? {
    tag: options.snapshot === true ? undefined : options.snapshot,
    commit: (_config$snapshot$prer = config.snapshot.prereleaseTemplate) !== null && _config$snapshot$prer !== void 0 && _config$snapshot$prer.includes("{commit}") ? await getCurrentCommitId({
      cwd
    }) : undefined
  } : undefined);
  let [...touchedFiles] = await applyReleasePlan(releasePlan, packages, releaseConfig, options.snapshot, __dirname);
  const [{
    getVersionMessage
  }, commitOpts] = await getCommitFunctions(releaseConfig.commit, cwd);
  if (getVersionMessage) {
    let touchedFile;
    // Note, git gets angry if you try and have two git actions running at once
    // So we need to be careful that these iterations are properly sequential
    while (touchedFile = touchedFiles.shift()) {
      await git.add(path.relative(cwd, touchedFile), cwd);
    }
    const commit = await git.commit(await getVersionMessage(releasePlan, commitOpts), cwd);
    if (!commit) {
      error("Changesets ran into trouble committing your files");
    } else {
      log("All files have been updated and committed. You're ready to publish!");
    }
  } else {
    log("All files have been updated. Review them and commit at your leisure");
  }
}

async function run(input, flags, cwd) {
  const packages = await getPackages(cwd);
  const rootDir = packages.root.dir;
  if (input[0] === "init") {
    await init(rootDir);
    return;
  }
  if (!fs.existsSync(path.resolve(rootDir, ".changeset"))) {
    error("There is no .changeset folder. ");
    error("If this is the first time `changesets` have been used in this project, run `yarn changeset init` to get set up.");
    error("If you expected there to be changesets, you should check git history for when the folder was removed to ensure you do not lose any configuration.");
    throw new ExitError(1);
  }
  let config;
  try {
    config = await read(rootDir, packages);
  } catch (e) {
    let oldConfigExists = await fs.pathExists(path.resolve(rootDir, ".changeset/config.js"));
    if (oldConfigExists) {
      error("It looks like you're using the version 1 `.changeset/config.js` file");
      error("You'll need to convert it to a `.changeset/config.json` file");
      error("The format of the config object has significantly changed in v2 as well");
      error(" - we thoroughly recommend looking at the changelog for this package for what has changed");
      throw new ExitError(1);
    } else {
      throw e;
    }
  }
  if (input.length < 1) {
    const {
      empty,
      open,
      since,
      message
    } = flags;
    // @ts-ignore if this is undefined, we have already exited
    await add(rootDir, {
      empty,
      open,
      since,
      message
    }, config);
  } else if (input[0] !== "pre" && input.length > 1) {
    error("Too many arguments passed to changesets - we only accept the command name as an argument");
  } else {
    const {
      sinceMaster,
      since,
      verbose,
      output,
      otp,
      empty,
      ignore,
      snapshot,
      snapshotPrereleaseTemplate,
      tag: tag$1,
      open,
      gitTag,
      message
    } = flags;
    const deadFlags = ["updateChangelog", "isPublic", "skipCI", "commit"];
    deadFlags.forEach(flag => {
      if (flags[flag]) {
        error(`the flag ${flag} has been removed from changesets for version 2`);
        error(`Please encode the desired value into your config`);
        error(`See our changelog for more details`);
        throw new ExitError(1);
      }
    });

    // Command line options need to be undefined, otherwise their
    // default value overrides the user's provided config in their
    // config file. For this reason, we only assign them to this
    // object as and when they exist.

    switch (input[0]) {
      case "add":
        {
          await add(rootDir, {
            empty,
            open,
            since,
            message
          }, config);
          return;
        }
      case "version":
        {
          let ignoreArrayFromCmd;
          if (typeof ignore === "string") {
            ignoreArrayFromCmd = [ignore];
          } else {
            // undefined or an array
            ignoreArrayFromCmd = ignore;
          }

          // Validate that items in ignoreArrayFromCmd are valid project names
          let pkgNames = new Set(packages.packages.map(({
            packageJson
          }) => packageJson.name));
          const messages = [];
          for (const pkgName of ignoreArrayFromCmd || []) {
            if (!pkgNames.has(pkgName)) {
              messages.push(`The package "${pkgName}" is passed to the \`--ignore\` option but it is not found in the project. You may have misspelled the package name.`);
            }
          }
          if (config.ignore.length > 0 && ignoreArrayFromCmd) {
            messages.push(`It looks like you are trying to use the \`--ignore\` option while ignore is defined in the config file. This is currently not allowed, you can only use one of them at a time.`);
          } else if (ignoreArrayFromCmd) {
            // use the ignore flags from cli
            config.ignore = ignoreArrayFromCmd;
          }
          const packagesByName = new Map(packages.packages.map(x => [x.packageJson.name, x]));

          // Validate that all dependents of skipped packages are also skipped.
          // devDependencies are excluded because they don't affect published consumers —
          // a stale devDep range on a skipped package is harmless.
          // Note: assemble-release-plan uses a graph WITH devDeps because it needs to
          // update devDep ranges in package.json even though they don't cause version bumps.
          const dependentsGraph = getDependentsGraph(packages, {
            ignoreDevDependencies: true,
            bumpVersionsWithWorkspaceProtocolOnly: config.bumpVersionsWithWorkspaceProtocolOnly
          });
          for (const pkg of packages.packages) {
            if (!shouldSkipPackage(pkg, {
              ignore: config.ignore,
              allowPrivatePackages: config.privatePackages.version
            })) {
              continue;
            }
            const skippedPackage = pkg.packageJson.name;
            const dependents = dependentsGraph.get(skippedPackage) || [];
            for (const dependent of dependents) {
              const dependentPkg = packagesByName.get(dependent);
              if (dependentPkg.packageJson.private) {
                // Private packages don't publish to npm,
                // so they can safely depend on skipped packages.
                // This also holds for private packages with other publish targets (like a VS Code extension)
                // as those typically have to prebundle dependencies.
                continue;
              }
              if (!shouldSkipPackage(dependentPkg, {
                ignore: config.ignore,
                allowPrivatePackages: config.privatePackages.version
              })) {
                messages.push(`The package "${dependent}" depends on the skipped package "${skippedPackage}" (either by \`ignore\` option or by \`privatePackages.version\`), but "${dependent}" is not being skipped. Please pass "${dependent}" to the \`--ignore\` flag.`);
              }
            }
          }
          if (messages.length > 0) {
            error(messages.join("\n"));
            throw new ExitError(1);
          }
          if (snapshotPrereleaseTemplate) {
            config.snapshot.prereleaseTemplate = snapshotPrereleaseTemplate;
          }
          await version(rootDir, {
            snapshot
          }, config);
          return;
        }
      case "publish":
        {
          await publish(rootDir, {
            otp,
            tag: tag$1,
            gitTag
          }, config);
          return;
        }
      case "status":
        {
          await status(rootDir, {
            sinceMaster,
            since,
            verbose,
            output
          }, config);
          return;
        }
      case "tag":
        {
          await tag(rootDir, config);
          return;
        }
      case "pre":
        {
          let command = input[1];
          if (command !== "enter" && command !== "exit") {
            error("`enter`, `exit` or `snapshot` must be passed after prerelease");
            throw new ExitError(1);
          }
          let tag = input[2];
          if (command === "enter" && typeof tag !== "string") {
            error(`A tag must be passed when using prerelease enter`);
            throw new ExitError(1);
          }
          await pre(rootDir, {
            command,
            tag
          });
          return;
        }
      case "bump":
        {
          error('In version 2 of changesets, "bump" has been renamed to "version" - see our changelog for an explanation');
          error("To fix this, use `changeset version` instead, and update any scripts that use changesets");
          throw new ExitError(1);
        }
      case "release":
        {
          error('In version 2 of changesets, "release" has been renamed to "publish" - see our changelog for an explanation');
          error("To fix this, use `changeset publish` instead, and update any scripts that use changesets");
          throw new ExitError(1);
        }
      default:
        {
          error(`Invalid command ${input[0]} was provided`);
          throw new ExitError(1);
        }
    }
  }
}

const args = process.argv.slice(2);
const parsed = mri(args, {
  boolean: ["sinceMaster", "verbose", "empty", "open", "gitTag", "snapshot"],
  string: ["output", "otp", "since", "ignore", "message", "tag", "snapshot", "snapshotPrereleaseTemplate"],
  alias: {
    // Short flags
    v: "verbose",
    o: "output",
    m: "message",
    // Support kebab-case flags
    "since-master": "sinceMaster",
    "git-tag": "gitTag",
    "snapshot-prerelease-template": "snapshotPrereleaseTemplate",
    // Deprecated flags
    "update-changelog": "updateChangelog",
    "is-public": "isPublic",
    "skip-c-i": "skipCI"
  },
  default: {
    gitTag: true
  }
});

// `mri` doesn't handle mixed boolean and strings well. It'll always try to coerce it as
// a string even if only `--snapshot` is passed. We check here if this was the case and
// try to coerce it as a boolean
if (parsed.snapshot === "" && args[args.indexOf("--snapshot") + 1] !== "") {
  parsed.snapshot = true;
}

// Help message should only be shown if it's the only argument passed
if (parsed.help && args.length === 1) {
  console.log(`
  Organise your package versioning and publishing to make both contributors and maintainers happy

  Usage
    $ changeset [command]
  Commands
    init
    add [--empty] [--open] [--since <branch>] [--message <text>]
    version [--ignore] [--snapshot <?name>] [--snapshot-prerelease-template <template>]
    publish [--tag <name>] [--otp <code>] [--no-git-tag]
    status [--since <branch>] [--verbose] [--output JSON_FILE.json]
    pre <enter|exit> <tag>
    tag

    `);
  process.exit(0);
}

// Version should only be shown if it's the only argument passed
if (parsed.version && args.length === 1) {
  // eslint-disable-next-line import/no-extraneous-dependencies
  console.log(require("@changesets/cli/package.json").version);
  process.exit(0);
}
const cwd = process.cwd();
run(parsed._, parsed, cwd).catch(err => {
  if (err instanceof InternalError) {
    error("The following error is an internal unexpected error, these should never happen.");
    error("Please open an issue with the following link");
    error(`https://github.com/changesets/changesets/issues/new?title=${encodeURIComponent(`Unexpected error during ${parsed._[0] || "add"} command`)}&body=${encodeURIComponent(`## Error

\`\`\`
${format("", err).replace(process.cwd(), "<cwd>")}
\`\`\`

## Versions

- @changesets/cli@${
    // eslint-disable-next-line import/no-extraneous-dependencies
    require("@changesets/cli/package.json").version}
- node@${process.version}

## Extra details

<!-- Add any extra details of what you were doing, ideas you have about what might have caused the error and reproduction steps if possible. If you have a repository we can look at that would be great. 😁 -->
`)}`);
  }
  if (err instanceof ExitError) {
    return process.exit(err.code);
  }
  error(err);
  process.exit(1);
});
