UNPKG

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