UNPKG

43.4 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var meow = _interopDefault(require('meow'));
6var errors = require('@changesets/errors');
7var logger = require('@changesets/logger');
8var util = require('util');
9var fs = _interopDefault(require('fs-extra'));
10var path = require('path');
11var path__default = _interopDefault(path);
12var getPackages = require('@manypkg/get-packages');
13var getDependentsGraph = require('@changesets/get-dependents-graph');
14var config = require('@changesets/config');
15var chalk = _interopDefault(require('chalk'));
16var termSize = _interopDefault(require('term-size'));
17var enquirer = require('enquirer');
18var externalEditor = require('external-editor');
19var git = require('@changesets/git');
20var writeChangeset = _interopDefault(require('@changesets/write'));
21var semver = _interopDefault(require('semver'));
22var boxen = _interopDefault(require('boxen'));
23var outdent = _interopDefault(require('outdent'));
24var applyReleasePlan = _interopDefault(require('@changesets/apply-release-plan'));
25var readChangesets = _interopDefault(require('@changesets/read'));
26var assembleReleasePlan = _interopDefault(require('@changesets/assemble-release-plan'));
27var pre$1 = require('@changesets/pre');
28var pLimit = _interopDefault(require('p-limit'));
29var preferredPM = _interopDefault(require('preferred-pm'));
30var spawn = _interopDefault(require('spawndamnit'));
31var isCI$1 = _interopDefault(require('is-ci'));
32var table = _interopDefault(require('tty-table'));
33var getReleasePlan = _interopDefault(require('@changesets/get-release-plan'));
34
35const pkgPath = path__default.dirname(require.resolve("@changesets/cli/package.json"));
36async function init(cwd) {
37 const changesetBase = path__default.resolve(cwd, ".changeset");
38
39 if (fs.existsSync(changesetBase)) {
40 if (!fs.existsSync(path__default.join(changesetBase, "config.json"))) {
41 if (fs.existsSync(path__default.join(changesetBase, "config.js"))) {
42 logger.error("It looks like you're using the version 1 `.changeset/config.js` file");
43 logger.error("The format of the config object has significantly changed in v2 as well");
44 logger.error(" - we thoroughly recommend looking at the changelog for this package for what has changed");
45 logger.error("Changesets will write the defaults for the new config, remember to transfer your options into the new config at `.changeset/config.json`");
46 } else {
47 logger.error("It looks like you don't have a config file");
48 logger.info("The default config file will be written at `.changeset/config.json`");
49 }
50
51 await fs.writeFile(path__default.resolve(changesetBase, "config.json"), JSON.stringify(config.defaultWrittenConfig, null, 2));
52 } else {
53 logger.warn("It looks like you already have changesets initialized. You should be able to run changeset commands no problems.");
54 }
55 } else {
56 await fs.copy(path__default.resolve(pkgPath, "./default-files"), changesetBase);
57 await fs.writeFile(path__default.resolve(changesetBase, "config.json"), JSON.stringify(config.defaultWrittenConfig, null, 2));
58 logger.log(chalk`Thanks for choosing {green changesets} to help manage your versioning and publishing\n`);
59 logger.log("You should be set up to start using changesets now!\n");
60 logger.info("We have added a `.changeset` folder, and a couple of files to help you out:");
61 logger.info(chalk`- {blue .changeset/README.md} contains information about using changesets`);
62 logger.info(chalk`- {blue .changeset/config.json} is our default config`);
63 }
64}
65
66// @ts-ignore it's not worth writing a TS declaration file in this repo for a tiny module we use once like this
67// so we can make type assertions using them because `enquirer` types do no support `prefix` right now
68
69/* Notes on using inquirer:
70 * Each question needs a key, as inquirer is assembling an object behind-the-scenes.
71 * At each call, the entire responses object is returned, so we need a unique
72 * identifier for the name every time. This is why we are using serial IDs
73 */
74const serialId = function () {
75 let id = 0;
76 return () => id++;
77}();
78
79const limit = Math.max(termSize().rows - 5, 10);
80
81let cancelFlow = () => {
82 logger.success("Cancelled... 👋 ");
83 process.exit();
84};
85
86async function askCheckboxPlus(message, choices, format) {
87 const name = `CheckboxPlus-${serialId()}`;
88 return enquirer.prompt({
89 type: "autocomplete",
90 name,
91 message,
92 prefix: logger.prefix,
93 multiple: true,
94 choices,
95 format,
96 limit,
97 onCancel: cancelFlow
98 }).then(responses => responses[name]).catch(err => {
99 logger.error(err);
100 });
101}
102
103async function askQuestion(message) {
104 const name = `Question-${serialId()}`;
105 return enquirer.prompt([{
106 type: "input",
107 message,
108 name,
109 prefix: logger.prefix,
110 onCancel: cancelFlow
111 }]).then(responses => responses[name]).catch(err => {
112 logger.error(err);
113 });
114}
115
116function askQuestionWithEditor(message) {
117 const response = externalEditor.edit(message, {
118 postfix: ".md"
119 });
120 return response.replace(/^#.*\n?/gm, "").replace(/\n+$/g, "").trim();
121}
122
123async function askConfirm(message) {
124 const name = `Confirm-${serialId()}`;
125 return enquirer.prompt([{
126 message,
127 name,
128 prefix: logger.prefix,
129 type: "confirm",
130 initial: true,
131 onCancel: cancelFlow
132 }]).then(responses => responses[name]).catch(err => {
133 logger.error(err);
134 });
135}
136
137async function askList(message, choices) {
138 const name = `List-${serialId()}`;
139 return enquirer.prompt([{
140 choices,
141 message,
142 name,
143 prefix: logger.prefix,
144 type: "select",
145 onCancel: cancelFlow
146 }]).then(responses => responses[name]).catch(err => {
147 logger.error(err);
148 });
149}
150
151const {
152 green,
153 yellow,
154 red,
155 bold,
156 blue,
157 cyan
158} = chalk;
159
160async function confirmMajorRelease(pkgJSON) {
161 if (semver.lt(pkgJSON.version, "1.0.0")) {
162 // prettier-ignore
163 logger.log(yellow(`WARNING: Releasing a major version for ${green(pkgJSON.name)} will be its ${red('first major release')}.`));
164 logger.log(yellow(`If you are unsure if this is correct, contact the package's maintainers ${red("before committing this changeset")}.`));
165 let shouldReleaseFirstMajor = await askConfirm(bold(`Are you sure you want still want to release the ${red("first major release")} of ${pkgJSON.name}?`));
166 return shouldReleaseFirstMajor;
167 }
168
169 return true;
170}
171
172async function getPackagesToRelease(changedPackages, allPackages) {
173 function askInitialReleaseQuestion(defaultChoiceList) {
174 return askCheckboxPlus( // TODO: Make this wording better
175 // TODO: take objects and be fancy with matching
176 `Which packages would you like to include?`, defaultChoiceList, x => {
177 // this removes changed packages and unchanged packages from the list
178 // of packages shown after selection
179 if (Array.isArray(x)) {
180 return x.filter(x => x !== "changed packages" && x !== "unchanged packages").map(x => cyan(x)).join(", ");
181 }
182
183 return x;
184 });
185 }
186
187 if (allPackages.length > 1) {
188 const unchangedPackagesNames = allPackages.map(({
189 packageJson
190 }) => packageJson.name).filter(name => !changedPackages.includes(name));
191 const defaultChoiceList = [{
192 name: "changed packages",
193 choices: changedPackages
194 }, {
195 name: "unchanged packages",
196 choices: unchangedPackagesNames
197 }].filter(({
198 choices
199 }) => choices.length !== 0);
200 let packagesToRelease = await askInitialReleaseQuestion(defaultChoiceList);
201
202 if (packagesToRelease.length === 0) {
203 do {
204 logger.error("You must select at least one package to release");
205 logger.error("(You most likely hit enter instead of space!)");
206 packagesToRelease = await askInitialReleaseQuestion(defaultChoiceList);
207 } while (packagesToRelease.length === 0);
208 }
209
210 return packagesToRelease.filter(pkgName => pkgName !== "changed packages" && pkgName !== "unchanged packages");
211 }
212
213 return [allPackages[0].packageJson.name];
214}
215
216function formatPkgNameAndVersion(pkgName, version) {
217 return `${bold(pkgName)}@${bold(version)}`;
218}
219
220async function createChangeset(changedPackages, allPackages) {
221 const releases = [];
222
223 if (allPackages.length > 1) {
224 const packagesToRelease = await getPackagesToRelease(changedPackages, allPackages);
225 let pkgJsonsByName = new Map(allPackages.map(({
226 packageJson
227 }) => [packageJson.name, packageJson]));
228 let pkgsLeftToGetBumpTypeFor = new Set(packagesToRelease);
229 let pkgsThatShouldBeMajorBumped = (await askCheckboxPlus(bold(`Which packages should have a ${red("major")} bump?`), [{
230 name: "all packages",
231 choices: packagesToRelease.map(pkgName => {
232 return {
233 name: pkgName,
234 message: formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version)
235 };
236 })
237 }], x => {
238 // this removes changed packages and unchanged packages from the list
239 // of packages shown after selection
240 if (Array.isArray(x)) {
241 return x.filter(x => x !== "all packages").map(x => cyan(x)).join(", ");
242 }
243
244 return x;
245 })).filter(x => x !== "all packages");
246
247 for (const pkgName of pkgsThatShouldBeMajorBumped) {
248 // for packages that are under v1, we want to make sure major releases are intended,
249 // as some repo-wide sweeping changes have mistakenly release first majors
250 // of packages.
251 let pkgJson = pkgJsonsByName.get(pkgName);
252 let shouldReleaseFirstMajor = await confirmMajorRelease(pkgJson);
253
254 if (shouldReleaseFirstMajor) {
255 pkgsLeftToGetBumpTypeFor.delete(pkgName);
256 releases.push({
257 name: pkgName,
258 type: "major"
259 });
260 }
261 }
262
263 if (pkgsLeftToGetBumpTypeFor.size !== 0) {
264 let pkgsThatShouldBeMinorBumped = (await askCheckboxPlus(bold(`Which packages should have a ${green("minor")} bump?`), [{
265 name: "all packages",
266 choices: [...pkgsLeftToGetBumpTypeFor].map(pkgName => {
267 return {
268 name: pkgName,
269 message: formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version)
270 };
271 })
272 }], x => {
273 // this removes changed packages and unchanged packages from the list
274 // of packages shown after selection
275 if (Array.isArray(x)) {
276 return x.filter(x => x !== "all packages").map(x => cyan(x)).join(", ");
277 }
278
279 return x;
280 })).filter(x => x !== "all packages");
281
282 for (const pkgName of pkgsThatShouldBeMinorBumped) {
283 pkgsLeftToGetBumpTypeFor.delete(pkgName);
284 releases.push({
285 name: pkgName,
286 type: "minor"
287 });
288 }
289 }
290
291 if (pkgsLeftToGetBumpTypeFor.size !== 0) {
292 logger.log(`The following packages will be ${blue("patch")} bumped:`);
293 pkgsLeftToGetBumpTypeFor.forEach(pkgName => {
294 logger.log(formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName).version));
295 });
296
297 for (const pkgName of pkgsLeftToGetBumpTypeFor) {
298 releases.push({
299 name: pkgName,
300 type: "patch"
301 });
302 }
303 }
304 } else {
305 let pkg = allPackages[0];
306 let type = await askList(`What kind of change is this for ${green(pkg.packageJson.name)}? (current version is ${pkg.packageJson.version})`, ["patch", "minor", "major"]);
307 console.log(type);
308
309 if (type === "major") {
310 let shouldReleaseAsMajor = await confirmMajorRelease(pkg.packageJson);
311
312 if (!shouldReleaseAsMajor) {
313 throw new errors.ExitError(1);
314 }
315 }
316
317 releases.push({
318 name: pkg.packageJson.name,
319 type
320 });
321 }
322
323 logger.log("Please enter a summary for this change (this will be in the changelogs). Submit empty line to open external editor");
324 let summary = await askQuestion("Summary");
325 if (summary.length === 0) summary = askQuestionWithEditor("");
326
327 try {
328 while (summary.length === 0) {
329 summary = await askQuestionWithEditor("\n\n# A summary is required for the changelog! 😪");
330 }
331 } catch (err) {
332 logger.log("An error happened using external editor. Please type your summary here:");
333 summary = await askQuestion("");
334
335 while (summary.length === 0) {
336 summary = await askQuestion("\n\n# A summary is required for the changelog! 😪");
337 }
338 }
339
340 return {
341 summary,
342 releases
343 };
344}
345
346function printConfirmationMessage(changeset, repoHasMultiplePackages) {
347 function getReleasesOfType(type) {
348 return changeset.releases.filter(release => release.type === type).map(release => release.name);
349 }
350
351 logger.log("=== Releasing the following packages ===");
352 const majorReleases = getReleasesOfType("major");
353 const minorReleases = getReleasesOfType("minor");
354 const patchReleases = getReleasesOfType("patch");
355 if (majorReleases.length > 0) logger.log(`${chalk.green("[Major]")}\n ${majorReleases.join(", ")}`);
356 if (minorReleases.length > 0) logger.log(`${chalk.green("[Minor]")}\n ${minorReleases.join(", ")}`);
357 if (patchReleases.length > 0) logger.log(`${chalk.green("[Patch]")}\n ${patchReleases.join(", ")}`);
358
359 if (repoHasMultiplePackages) {
360 const message = outdent`
361 ${chalk.red("========= NOTE ========")}
362 All dependents of these packages that will be incompatible with the new version will be ${chalk.red("patch bumped")} when this changeset is applied.`;
363 const prettyMessage = boxen(message, {
364 borderStyle: "double",
365 align: "center"
366 });
367 logger.log(prettyMessage);
368 }
369}
370
371async function add(cwd, {
372 empty
373}, config) {
374 const packages = await getPackages.getPackages(cwd);
375 const changesetBase = path__default.resolve(cwd, ".changeset");
376 let newChangeset, confirmChangeset;
377
378 if (empty) {
379 newChangeset = {
380 releases: [],
381 summary: ``
382 };
383 confirmChangeset = true;
384 } else {
385 const changedPackages = await git.getChangedPackagesSinceRef({
386 cwd,
387 ref: config.baseBranch
388 });
389 const changePackagesName = changedPackages.filter(a => a).map(pkg => pkg.packageJson.name);
390 newChangeset = await createChangeset(changePackagesName, packages.packages);
391 printConfirmationMessage(newChangeset, packages.packages.length > 1);
392 confirmChangeset = await askConfirm("Is this your desired changeset?");
393 }
394
395 if (confirmChangeset) {
396 const changesetID = await writeChangeset(newChangeset, cwd);
397
398 if (config.commit) {
399 await git.add(path__default.resolve(changesetBase, `${changesetID}.md`), cwd);
400 await git.commit(`docs(changeset): ${newChangeset.summary}`, cwd);
401 logger.log(chalk.green(`${empty ? "Empty " : ""}Changeset added and committed`));
402 } else {
403 logger.log(chalk.green(`${empty ? "Empty " : ""}Changeset added! - you can now commit it\n`));
404 }
405
406 let hasMajorChange = [...newChangeset.releases].find(c => c.type === "major");
407
408 if (hasMajorChange) {
409 logger.warn("This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:");
410 logger.warn("WHAT the breaking change is");
411 logger.warn("WHY the change was made");
412 logger.warn("HOW a consumer should update their code");
413 } else {
414 logger.log(chalk.green("If you want to modify or expand on the changeset summary, you can find it here"));
415 }
416
417 logger.info(chalk.blue(path__default.resolve(changesetBase, `${changesetID}.md`)));
418 }
419}
420
421// folder, and tidy up the subfolders
422// THIS SHOULD BE REMOVED WHEN SUPPORT FOR CHANGESETS FROM V1 IS DROPPED
423
424const removeEmptyFolders = async folderPath => {
425 const dirContents = fs.readdirSync(folderPath);
426 return Promise.all(dirContents.map(async contentPath => {
427 const singleChangesetPath = path__default.resolve(folderPath, contentPath);
428
429 try {
430 if ((await fs.readdir(singleChangesetPath)).length < 1) {
431 await fs.rmdir(singleChangesetPath);
432 }
433 } catch (err) {
434 if (err.code !== "ENOTDIR") {
435 throw err;
436 }
437 }
438 }));
439};
440
441function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
442
443function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
444
445function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
446let importantSeparator = chalk.red("===============================IMPORTANT!===============================");
447let importantEnd = chalk.red("----------------------------------------------------------------------");
448async function version(cwd, options, config) {
449 const releaseConfig = _objectSpread(_objectSpread({}, config), {}, {
450 // Disable committing when in snapshot mode
451 commit: options.snapshot ? false : config.commit
452 });
453
454 const [changesets, preState] = await Promise.all([readChangesets(cwd), pre$1.readPreState(cwd), removeEmptyFolders(path__default.resolve(cwd, ".changeset"))]);
455
456 if ((preState === null || preState === void 0 ? void 0 : preState.mode) === "pre") {
457 logger.warn(importantSeparator);
458
459 if (options.snapshot !== undefined) {
460 logger.error("Snapshot release is not allowed in pre mode");
461 logger.log("To resolve this exit the pre mode by running `changeset pre exit`");
462 throw new errors.ExitError(1);
463 } else {
464 logger.warn("You are in prerelease mode");
465 logger.warn("If you meant to do a normal release you should revert these changes and run `changeset pre exit`");
466 logger.warn("You can then run `changeset version` again to do a normal release");
467 }
468
469 logger.warn(importantEnd);
470 }
471
472 if (changesets.length === 0 && (preState === undefined || preState.mode !== "exit")) {
473 logger.warn("No unreleased changesets found, exiting.");
474 return;
475 }
476
477 let packages = await getPackages.getPackages(cwd);
478 let releasePlan = assembleReleasePlan(changesets, packages, releaseConfig, preState, options.snapshot);
479 await applyReleasePlan(releasePlan, packages, releaseConfig, options.snapshot);
480
481 if (releaseConfig.commit) {
482 logger.log("All files have been updated and committed. You're ready to publish!");
483 } else {
484 logger.log("All files have been updated. Review them and commit at your leisure");
485 }
486}
487
488// @ts-ignore
489var isCI = !!(isCI$1 || process.env.GITHUB_ACTIONS);
490
491const npmRequestLimit = pLimit(40);
492
493function jsonParse(input) {
494 try {
495 return JSON.parse(input);
496 } catch (err) {
497 if (err instanceof SyntaxError) {
498 console.error("error parsing json:", input);
499 }
500
501 throw err;
502 }
503}
504
505function getCorrectRegistry() {
506 let registry = process.env.npm_config_registry === "https://registry.yarnpkg.com" ? undefined : process.env.npm_config_registry;
507 return registry;
508}
509
510async function getPublishTool(cwd) {
511 const pm = await preferredPM(cwd);
512 if (!pm || pm.name !== "pnpm") return {
513 name: "npm"
514 };
515
516 try {
517 let result = await spawn("pnpm", ["--version"], {
518 cwd
519 });
520 let version = result.stdout.toString().trim();
521 let parsed = semver.parse(version);
522 return {
523 name: "pnpm",
524 shouldAddNoGitChecks: (parsed === null || parsed === void 0 ? void 0 : parsed.major) === undefined ? false : parsed.major >= 5
525 };
526 } catch (e) {
527 return {
528 name: "pnpm",
529 shouldAddNoGitChecks: false
530 };
531 }
532}
533
534async function getTokenIsRequired() {
535 // Due to a super annoying issue in yarn, we have to manually override this env variable
536 // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
537 const envOverride = {
538 npm_config_registry: getCorrectRegistry()
539 };
540 let result = await spawn("npm", ["profile", "get", "--json"], {
541 env: Object.assign({}, process.env, envOverride)
542 });
543 let json = jsonParse(result.stdout.toString());
544
545 if (json.error || !json.tfa || !json.tfa.mode) {
546 return false;
547 }
548
549 return json.tfa.mode === "auth-and-writes";
550}
551function getPackageInfo(pkgName) {
552 return npmRequestLimit(async () => {
553 logger.info(`npm info ${pkgName}`); // Due to a couple of issues with yarnpkg, we also want to override the npm registry when doing
554 // npm info.
555 // Issues: We sometimes get back cached responses, i.e old data about packages which causes
556 // `publish` to behave incorrectly. It can also cause issues when publishing private packages
557 // as they will always give a 404, which will tell `publish` to always try to publish.
558 // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
559
560 const envOverride = {
561 npm_config_registry: getCorrectRegistry()
562 };
563 let result = await spawn("npm", ["info", pkgName, "--json"], {
564 env: Object.assign({}, process.env, envOverride)
565 });
566 return jsonParse(result.stdout.toString());
567 });
568}
569async function infoAllow404(pkgName) {
570 let pkgInfo = await getPackageInfo(pkgName);
571
572 if (pkgInfo.error && pkgInfo.error.code === "E404") {
573 logger.warn(`Received 404 for npm info ${chalk.cyan(`"${pkgName}"`)}`);
574 return {
575 published: false,
576 pkgInfo: {}
577 };
578 }
579
580 if (pkgInfo.error) {
581 logger.error(`Received an unknown error code: ${pkgInfo.error.code} for npm info ${chalk.cyan(`"${pkgName}"`)}`);
582 logger.error(pkgInfo.error.summary);
583 if (pkgInfo.error.detail) logger.error(pkgInfo.error.detail);
584 throw new errors.ExitError(1);
585 }
586
587 return {
588 published: true,
589 pkgInfo
590 };
591}
592let otpAskLimit = pLimit(1);
593
594let askForOtpCode = twoFactorState => otpAskLimit(async () => {
595 if (twoFactorState.token !== null) return twoFactorState.token;
596 logger.info("This operation requires a one-time password from your authenticator.");
597 let val = await askQuestion("Enter one-time password:");
598 twoFactorState.token = val;
599 return val;
600});
601
602let getOtpCode = async twoFactorState => {
603 if (twoFactorState.token !== null) {
604 return twoFactorState.token;
605 }
606
607 return askForOtpCode(twoFactorState);
608}; // we have this so that we can do try a publish again after a publish without
609// the call being wrapped in the npm request limit and causing the publishes to potentially never run
610
611async function internalPublish(pkgName, opts, twoFactorState) {
612 let publishTool = await getPublishTool(opts.cwd);
613 let publishFlags = opts.access ? ["--access", opts.access] : [];
614 publishFlags.push("--tag", opts.tag);
615
616 if ((await twoFactorState.isRequired) && !isCI) {
617 let otpCode = await getOtpCode(twoFactorState);
618 publishFlags.push("--otp", otpCode);
619 }
620
621 if (publishTool.name === "pnpm" && publishTool.shouldAddNoGitChecks) {
622 publishFlags.push("--no-git-checks");
623 } // Due to a super annoying issue in yarn, we have to manually override this env variable
624 // See: https://github.com/yarnpkg/yarn/issues/2935#issuecomment-355292633
625
626
627 const envOverride = {
628 npm_config_registry: getCorrectRegistry()
629 };
630 let {
631 stdout
632 } = await spawn(publishTool.name, ["publish", "--json", ...publishFlags], {
633 cwd: opts.cwd,
634 env: Object.assign({}, process.env, envOverride)
635 }); // New error handling. NPM's --json option is included alongside the `prepublish and
636 // `postpublish` contents in terminal. We want to handle this as best we can but it has
637 // some struggles
638 // Note that both pre and post publish hooks are printed before the json out, so this works.
639
640 let json = jsonParse(stdout.toString().replace(/[^{]*/, ""));
641
642 if (json.error) {
643 // The first case is no 2fa provided, the second is when the 2fa is wrong (timeout or wrong words)
644 if ((json.error.code === "EOTP" || json.error.code === "E401" && json.error.detail.includes("--otp=<code>")) && !isCI) {
645 if (twoFactorState.token !== null) {
646 // the current otp code must be invalid since it errored
647 twoFactorState.token = null;
648 } // just in case this isn't already true
649
650
651 twoFactorState.isRequired = Promise.resolve(true);
652 return internalPublish(pkgName, opts, twoFactorState);
653 }
654
655 logger.error(`an error occurred while publishing ${pkgName}: ${json.error.code}`, json.error.summary, json.error.detail ? "\n" + json.error.detail : "");
656 return {
657 published: false
658 };
659 }
660
661 return {
662 published: true
663 };
664}
665
666function publish(pkgName, opts, twoFactorState) {
667 return npmRequestLimit(() => {
668 return internalPublish(pkgName, opts, twoFactorState);
669 });
670}
671
672function getReleaseTag(pkgInfo, preState, tag) {
673 if (tag) return tag;
674
675 if (preState !== undefined && pkgInfo.publishedState !== "only-pre") {
676 return preState.tag;
677 }
678
679 return "latest";
680}
681
682async function publishPackages({
683 packages,
684 access,
685 otp,
686 preState,
687 tag
688}) {
689 const packagesByName = new Map(packages.map(x => [x.packageJson.name, x]));
690 const publicPackages = packages.filter(pkg => !pkg.packageJson.private);
691 let twoFactorState = otp === undefined ? {
692 token: null,
693 isRequired: isCI || publicPackages.some(x => x.packageJson.publishConfig && x.packageJson.publishConfig.registry && x.packageJson.publishConfig.registry !== "https://registry.npmjs.org" && x.packageJson.publishConfig.registry !== "https://registry.yarnpkg.com") || process.env.npm_config_registry !== undefined && process.env.npm_config_registry !== "https://registry.npmjs.org" && process.env.npm_config_registry !== "https://registry.yarnpkg.com" ? Promise.resolve(false) : // note: we're not awaiting this here, we want this request to happen in parallel with getUnpublishedPackages
694 getTokenIsRequired()
695 } : {
696 token: otp,
697 isRequired: Promise.resolve(true)
698 };
699 const unpublishedPackagesInfo = await getUnpublishedPackages(publicPackages, preState);
700
701 if (unpublishedPackagesInfo.length === 0) {
702 logger.warn("No unpublished packages to publish");
703 }
704
705 let promises = [];
706
707 for (let pkgInfo of unpublishedPackagesInfo) {
708 let pkg = packagesByName.get(pkgInfo.name);
709 promises.push(publishAPackage(pkg, access, twoFactorState, getReleaseTag(pkgInfo, preState, tag)));
710 }
711
712 return Promise.all(promises);
713}
714
715async function publishAPackage(pkg, access, twoFactorState, tag) {
716 const {
717 name,
718 version,
719 publishConfig
720 } = pkg.packageJson;
721 const localAccess = publishConfig && publishConfig.access;
722 logger.info(`Publishing ${chalk.cyan(`"${name}"`)} at ${chalk.green(`"${version}"`)}`);
723 const publishDir = publishConfig && publishConfig.directory ? path.join(pkg.dir, publishConfig.directory) : pkg.dir;
724 const publishConfirmation = await publish(name, {
725 cwd: publishDir,
726 access: localAccess || access,
727 tag
728 }, twoFactorState);
729 return {
730 name,
731 newVersion: version,
732 published: publishConfirmation.published
733 };
734}
735
736async function getUnpublishedPackages(packages, preState) {
737 const results = await Promise.all(packages.map(async pkg => {
738 const config = pkg.packageJson;
739 const response = await infoAllow404(config.name);
740 let publishedState = "never";
741
742 if (response.published) {
743 publishedState = "published";
744
745 if (preState !== undefined) {
746 if (response.pkgInfo.versions && response.pkgInfo.versions.every(version => semver.parse(version).prerelease[0] === preState.tag)) {
747 publishedState = "only-pre";
748 }
749 }
750 }
751
752 return {
753 name: config.name,
754 localVersion: config.version,
755 publishedState: publishedState,
756 publishedVersions: response.pkgInfo.versions || []
757 };
758 }));
759 const packagesToPublish = [];
760
761 for (const pkgInfo of results) {
762 const {
763 name,
764 publishedState,
765 localVersion,
766 publishedVersions
767 } = pkgInfo;
768
769 if (!publishedVersions.includes(localVersion)) {
770 packagesToPublish.push(pkgInfo);
771 logger.info(`${name} is being published because our local version (${localVersion}) has not been published on npm`);
772
773 if (preState !== undefined && publishedState === "only-pre") {
774 logger.info(`${name} is being published to ${chalk.cyan("latest")} rather than ${chalk.cyan(preState.tag)} because there has not been a regular release of it yet`);
775 }
776 } else {
777 // If the local version is behind npm, something is wrong, we warn here, and by not getting published later, it will fail
778 logger.warn(`${name} is not being published because version ${localVersion} is already published on npm`);
779 }
780 }
781
782 return packagesToPublish;
783}
784
785function logReleases(pkgs) {
786 const mappedPkgs = pkgs.map(p => `${p.name}@${p.newVersion}`).join("\n");
787 logger.log(mappedPkgs);
788}
789
790let importantSeparator$1 = chalk.red("===============================IMPORTANT!===============================");
791let importantEnd$1 = chalk.red("----------------------------------------------------------------------");
792
793function showNonLatestTagWarning(tag, preState) {
794 logger.warn(importantSeparator$1);
795
796 if (preState) {
797 logger.warn(`You are in prerelease mode so packages will be published to the ${chalk.cyan(preState.tag)}
798 dist tag except for packages that have not had normal releases which will be published to ${chalk.cyan("latest")}`);
799 } else if (tag !== "latest") {
800 logger.warn(`Packages will be released under the ${tag} tag`);
801 }
802
803 logger.warn(importantEnd$1);
804}
805
806async function run(cwd, {
807 otp,
808 tag
809}, config) {
810 const releaseTag = tag && tag.length > 0 ? tag : undefined;
811 let preState = await pre$1.readPreState(cwd);
812
813 if (releaseTag && preState && preState.mode === "pre") {
814 logger.error("Releasing under custom tag is not allowed in pre mode");
815 logger.log("To resolve this exit the pre mode by running `changeset pre exit`");
816 throw new errors.ExitError(1);
817 }
818
819 if (releaseTag || preState) {
820 showNonLatestTagWarning(tag, preState);
821 }
822
823 const {
824 packages,
825 tool
826 } = await getPackages.getPackages(cwd);
827 const response = await publishPackages({
828 packages,
829 // if not public, we wont pass the access, and it works as normal
830 access: config.access,
831 otp,
832 preState,
833 tag: releaseTag
834 });
835 const successful = response.filter(p => p.published);
836 const unsuccessful = response.filter(p => !p.published);
837
838 if (successful.length > 0) {
839 logger.success("packages published successfully:");
840 logReleases(successful); // We create the tags after the push above so that we know that HEAD wont change and that pushing
841 // wont suffer from a race condition if another merge happens in the mean time (pushing tags wont
842 // fail if we are behind master).
843
844 logger.log(`Creating git tag${successful.length > 1 ? "s" : ""}...`);
845
846 if (tool !== "root") {
847 for (const pkg of successful) {
848 const tag = `${pkg.name}@${pkg.newVersion}`;
849 logger.log("New tag: ", tag);
850 await git.tag(tag, cwd);
851 }
852 } else {
853 const tag = `v${successful[0].newVersion}`;
854 logger.log("New tag: ", tag);
855 await git.tag(tag, cwd);
856 }
857 }
858
859 if (unsuccessful.length > 0) {
860 logger.error("packages failed to publish:");
861 logReleases(unsuccessful);
862 throw new errors.ExitError(1);
863 }
864}
865
866async function getStatus(cwd, {
867 sinceMaster,
868 since,
869 verbose,
870 output
871}, config) {
872 if (sinceMaster) {
873 logger.warn("--sinceMaster is deprecated and will be removed in a future major version");
874 logger.warn("Use --since=master instead");
875 }
876
877 const releasePlan = await getReleasePlan(cwd, since === undefined ? sinceMaster ? "master" : undefined : since, config);
878 const {
879 changesets,
880 releases
881 } = releasePlan;
882
883 if (changesets.length < 1) {
884 logger.error("No changesets present");
885 process.exit(1);
886 }
887
888 if (output) {
889 await fs.writeFile(path__default.join(cwd, output), JSON.stringify(releasePlan, undefined, 2));
890 return;
891 }
892
893 const print = verbose ? verbosePrint : SimplePrint;
894 print("patch", releases);
895 logger.log("---");
896 print("minor", releases);
897 logger.log("---");
898 print("major", releases);
899 return releasePlan;
900}
901
902function SimplePrint(type, releases) {
903 const packages = releases.filter(r => r.type === type);
904
905 if (packages.length) {
906 logger.info(chalk`Packages to be bumped at {green ${type}}:\n`);
907 const pkgs = packages.map(({
908 name
909 }) => `- ${name}`).join("\n");
910 logger.log(chalk.green(pkgs));
911 } else {
912 logger.info(chalk`{red NO} packages to be bumped at {green ${type}}`);
913 }
914}
915
916function verbosePrint(type, releases) {
917 const packages = releases.filter(r => r.type === type);
918
919 if (packages.length) {
920 logger.info(chalk`Packages to be bumped at {green ${type}}`);
921 const columns = packages.map(({
922 name,
923 newVersion: version,
924 changesets
925 }) => [chalk.green(name), version, changesets.map(c => chalk.blue(` .changeset/${c}/changes.md`)).join(" +")]);
926 const t1 = table([{
927 value: "Package Name",
928 width: 20
929 }, {
930 value: "New Version",
931 width: 20
932 }, {
933 value: "Related Changeset Summaries",
934 width: 70
935 }], columns, {
936 paddingLeft: 1,
937 paddingRight: 0,
938 headerAlign: "center",
939 align: "left"
940 });
941 logger.log(t1.render() + "\n");
942 } else {
943 logger.info(chalk`Running release would release {red NO} packages as a {green ${type}}`);
944 }
945}
946
947async function pre(cwd, options) {
948 if (options.command === "enter") {
949 try {
950 await pre$1.enterPre(cwd, options.tag);
951 logger.success(`Entered pre mode with tag ${chalk.cyan(options.tag)}`);
952 logger.info("Run `changeset version` to version packages with prerelease versions");
953 } catch (err) {
954 if (err instanceof errors.PreEnterButInPreModeError) {
955 logger.error("`changeset pre enter` cannot be run when in pre mode");
956 logger.info("If you're trying to exit pre mode, run `changeset pre exit`");
957 throw new errors.ExitError(1);
958 }
959
960 throw err;
961 }
962 } else {
963 try {
964 await pre$1.exitPre(cwd);
965 logger.success(`Exited pre mode`);
966 logger.info("Run `changeset version` to version packages with normal versions");
967 } catch (err) {
968 if (err instanceof errors.PreExitButNotInPreModeError) {
969 logger.error("`changeset pre exit` can only be run when in pre mode");
970 logger.info("If you're trying to enter pre mode, run `changeset pre enter`");
971 throw new errors.ExitError(1);
972 }
973
974 throw err;
975 }
976 }
977}
978
979async function run$1(input, flags, cwd) {
980 if (input[0] === "init") {
981 await init(cwd);
982 return;
983 }
984
985 if (!fs.existsSync(path__default.resolve(cwd, ".changeset"))) {
986 logger.error("There is no .changeset folder. ");
987 logger.error("If this is the first time `changesets` have been used in this project, run `yarn changeset init` to get set up.");
988 logger.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.");
989 throw new errors.ExitError(1);
990 }
991
992 const packages = await getPackages.getPackages(cwd);
993 let config$1;
994
995 try {
996 config$1 = await config.read(cwd, packages);
997 } catch (e) {
998 let oldConfigExists = await fs.pathExists(path__default.resolve(cwd, ".changeset/config.js"));
999
1000 if (oldConfigExists) {
1001 logger.error("It looks like you're using the version 1 `.changeset/config.js` file");
1002 logger.error("You'll need to convert it to a `.changeset/config.json` file");
1003 logger.error("The format of the config object has significantly changed in v2 as well");
1004 logger.error(" - we thoroughly recommend looking at the changelog for this package for what has changed");
1005 throw new errors.ExitError(1);
1006 } else {
1007 throw e;
1008 }
1009 }
1010
1011 if (input.length < 1) {
1012 const {
1013 empty
1014 } = flags; // @ts-ignore if this is undefined, we have already exited
1015
1016 await add(cwd, {
1017 empty
1018 }, config$1);
1019 } else if (input[0] !== "pre" && input.length > 1) {
1020 logger.error("Too many arguments passed to changesets - we only accept the command name as an argument");
1021 } else {
1022 const {
1023 sinceMaster,
1024 since,
1025 verbose,
1026 output,
1027 otp,
1028 empty,
1029 ignore,
1030 snapshot,
1031 tag
1032 } = flags;
1033 const deadFlags = ["updateChangelog", "isPublic", "skipCI", "commit"];
1034 deadFlags.forEach(flag => {
1035 if (flags[flag]) {
1036 logger.error(`the flag ${flag} has been removed from changesets for version 2`);
1037 logger.error(`Please encode the desired value into your config`);
1038 logger.error(`See our changelog for more details`);
1039 throw new errors.ExitError(1);
1040 }
1041 }); // Command line options need to be undefined, otherwise their
1042 // default value overrides the user's provided config in their
1043 // config file. For this reason, we only assign them to this
1044 // object as and when they exist.
1045
1046 switch (input[0]) {
1047 case "add":
1048 {
1049 // @ts-ignore if this is undefined, we have already exited
1050 await add(cwd, {
1051 empty
1052 }, config$1);
1053 return;
1054 }
1055
1056 case "version":
1057 {
1058 let ignoreArrayFromCmd;
1059
1060 if (typeof ignore === "string") {
1061 ignoreArrayFromCmd = [ignore];
1062 } else {
1063 // undefined or an array
1064 ignoreArrayFromCmd = ignore;
1065 } // Validate that items in ignoreArrayFromCmd are valid project names
1066
1067
1068 let pkgNames = new Set(packages.packages.map(({
1069 packageJson
1070 }) => packageJson.name));
1071 const messages = [];
1072
1073 for (const pkgName of ignoreArrayFromCmd || []) {
1074 if (!pkgNames.has(pkgName)) {
1075 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.`);
1076 }
1077 }
1078
1079 if (config$1.ignore.length > 0 && ignoreArrayFromCmd) {
1080 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.`);
1081 } else if (ignoreArrayFromCmd) {
1082 // use the ignore flags from cli
1083 config$1.ignore = ignoreArrayFromCmd;
1084 } // Validate that all dependents of ignored packages are listed in the ignore list
1085
1086
1087 const dependentsGraph = getDependentsGraph.getDependentsGraph(packages);
1088
1089 for (const ignoredPackage of config$1.ignore) {
1090 const dependents = dependentsGraph.get(ignoredPackage) || [];
1091
1092 for (const dependent of dependents) {
1093 if (!config$1.ignore.includes(dependent)) {
1094 messages.push(`The package "${dependent}" depends on the ignored package "${ignoredPackage}", but "${dependent}" is not being ignored. Please pass "${dependent}" to the \`--ignore\` flag.`);
1095 }
1096 }
1097 }
1098
1099 if (messages.length > 0) {
1100 logger.error(messages.join("\n"));
1101 throw new errors.ExitError(1);
1102 }
1103
1104 await version(cwd, {
1105 snapshot
1106 }, config$1);
1107 return;
1108 }
1109
1110 case "publish":
1111 {
1112 await run(cwd, {
1113 otp,
1114 tag
1115 }, config$1);
1116 return;
1117 }
1118
1119 case "status":
1120 {
1121 await getStatus(cwd, {
1122 sinceMaster,
1123 since,
1124 verbose,
1125 output
1126 }, config$1);
1127 return;
1128 }
1129
1130 case "pre":
1131 {
1132 let command = input[1];
1133
1134 if (command !== "enter" && command !== "exit") {
1135 logger.error("`enter`, `exit` or `snapshot` must be passed after prerelease");
1136 throw new errors.ExitError(1);
1137 }
1138
1139 let tag = input[2];
1140
1141 if (command === "enter" && typeof tag !== "string") {
1142 logger.error(`A tag must be passed when using prerelese enter`);
1143 throw new errors.ExitError(1);
1144 } // @ts-ignore
1145
1146
1147 await pre(cwd, {
1148 command,
1149 tag
1150 });
1151 return;
1152 }
1153
1154 case "bump":
1155 {
1156 logger.error('In version 2 of changesets, "bump" has been renamed to "version" - see our changelog for an explanation');
1157 logger.error("To fix this, use `changeset version` instead, and update any scripts that use changesets");
1158 throw new errors.ExitError(1);
1159 }
1160
1161 case "release":
1162 {
1163 logger.error('In version 2 of changesets, "release" has been renamed to "publish" - see our changelog for an explanation');
1164 logger.error("To fix this, use `changeset publish` instead, and update any scripts that use changesets");
1165 throw new errors.ExitError(1);
1166 }
1167
1168 default:
1169 {
1170 logger.error(`Invalid command ${input[0]} was provided`);
1171 throw new errors.ExitError(1);
1172 }
1173 }
1174 }
1175}
1176
1177const {
1178 input,
1179 flags
1180} = meow(`
1181 Usage
1182 $ changesets [command]
1183 Commands
1184 init
1185 add [--empty]
1186 version [--ignore]
1187 publish [--otp=code]
1188 status [--since-master --verbose --output=JSON_FILE.json]
1189 prerelease <tag>
1190 `, {
1191 flags: {
1192 sinceMaster: {
1193 type: "boolean"
1194 },
1195 verbose: {
1196 type: "boolean",
1197 alias: "v"
1198 },
1199 output: {
1200 type: "string",
1201 alias: "o"
1202 },
1203 otp: {
1204 type: "string",
1205 default: undefined
1206 },
1207 empty: {
1208 type: "boolean"
1209 },
1210 since: {
1211 type: "string",
1212 default: undefined
1213 },
1214 ignore: {
1215 type: "string",
1216 default: undefined,
1217 isMultiple: true
1218 },
1219 tag: {
1220 type: "string"
1221 }
1222 }
1223});
1224const cwd = process.cwd();
1225run$1(input, flags, cwd).catch(err => {
1226 if (err instanceof errors.InternalError) {
1227 logger.error("The following error is an internal unexpected error, these should never happen.");
1228 logger.error("Please open an issue with the following link");
1229 logger.error(`https://github.com/atlassian/changesets/issues/new?title=${encodeURIComponent(`Unexpected error during ${input[0] || "add"} command`)}&body=${encodeURIComponent(`## Error
1230
1231\`\`\`
1232${util.format("", err).replace(process.cwd(), "<cwd>")}
1233\`\`\`
1234
1235## Versions
1236
1237- @changesets/cli@${// eslint-disable-next-line import/no-extraneous-dependencies
1238 require("@changesets/cli/package.json").version}
1239- node@${process.version}
1240
1241## Extra details
1242
1243<!-- 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. 😁 -->
1244`)}`);
1245 }
1246
1247 if (err instanceof errors.ExitError) {
1248 return process.exit(err.code);
1249 }
1250
1251 logger.error(err);
1252 process.exit(1);
1253});