@yarnpkg/plugin-essentials 4.0.0-rc.5 → 4.0.0-rc.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/lib/commands/add.d.ts +1 -1
  2. package/lib/commands/add.js +34 -25
  3. package/lib/commands/bin.d.ts +1 -1
  4. package/lib/commands/bin.js +1 -1
  5. package/lib/commands/cache/clean.d.ts +1 -1
  6. package/lib/commands/cache/clean.js +1 -1
  7. package/lib/commands/config/get.d.ts +1 -1
  8. package/lib/commands/config/get.js +1 -1
  9. package/lib/commands/config/set.d.ts +1 -1
  10. package/lib/commands/config/set.js +1 -1
  11. package/lib/commands/config/unset.d.ts +1 -1
  12. package/lib/commands/config/unset.js +1 -1
  13. package/lib/commands/config.d.ts +1 -1
  14. package/lib/commands/config.js +1 -1
  15. package/lib/commands/dedupe.d.ts +1 -1
  16. package/lib/commands/dedupe.js +7 -8
  17. package/lib/commands/entries/clipanion.js +1 -1
  18. package/lib/commands/entries/help.js +1 -1
  19. package/lib/commands/entries/version.js +1 -1
  20. package/lib/commands/exec.js +1 -1
  21. package/lib/commands/explain/peerRequirements.d.ts +2 -2
  22. package/lib/commands/explain/peerRequirements.js +4 -6
  23. package/lib/commands/explain.js +1 -1
  24. package/lib/commands/info.js +10 -18
  25. package/lib/commands/install.d.ts +1 -1
  26. package/lib/commands/install.js +188 -107
  27. package/lib/commands/link.d.ts +2 -2
  28. package/lib/commands/link.js +36 -32
  29. package/lib/commands/node.js +1 -1
  30. package/lib/commands/plugin/check.d.ts +8 -0
  31. package/lib/commands/plugin/check.js +61 -0
  32. package/lib/commands/plugin/import/sources.d.ts +3 -3
  33. package/lib/commands/plugin/import/sources.js +5 -5
  34. package/lib/commands/plugin/import.d.ts +5 -4
  35. package/lib/commands/plugin/import.js +15 -28
  36. package/lib/commands/plugin/list.d.ts +2 -2
  37. package/lib/commands/plugin/list.js +5 -6
  38. package/lib/commands/plugin/remove.d.ts +1 -1
  39. package/lib/commands/plugin/remove.js +14 -13
  40. package/lib/commands/plugin/runtime.d.ts +2 -2
  41. package/lib/commands/plugin/runtime.js +4 -4
  42. package/lib/commands/rebuild.d.ts +2 -2
  43. package/lib/commands/rebuild.js +10 -10
  44. package/lib/commands/remove.d.ts +1 -1
  45. package/lib/commands/remove.js +7 -8
  46. package/lib/commands/run.js +1 -1
  47. package/lib/commands/runIndex.d.ts +2 -2
  48. package/lib/commands/runIndex.js +3 -3
  49. package/lib/commands/set/resolution.d.ts +1 -1
  50. package/lib/commands/set/resolution.js +4 -6
  51. package/lib/commands/set/version/sources.d.ts +3 -2
  52. package/lib/commands/set/version/sources.js +21 -12
  53. package/lib/commands/set/version.d.ts +2 -3
  54. package/lib/commands/set/version.js +20 -16
  55. package/lib/commands/unlink.d.ts +1 -1
  56. package/lib/commands/unlink.js +7 -8
  57. package/lib/commands/up.d.ts +3 -3
  58. package/lib/commands/up.js +19 -17
  59. package/lib/commands/why.js +5 -13
  60. package/lib/commands/workspace.js +2 -5
  61. package/lib/commands/workspaces/list.d.ts +2 -1
  62. package/lib/commands/workspaces/list.js +8 -1
  63. package/lib/dedupeUtils.d.ts +3 -3
  64. package/lib/dedupeUtils.js +3 -5
  65. package/lib/index.d.ts +76 -1
  66. package/lib/index.js +40 -1
  67. package/lib/suggestUtils.d.ts +4 -4
  68. package/lib/suggestUtils.js +14 -12
  69. package/package.json +23 -17
@@ -7,7 +7,21 @@ const fslib_1 = require("@yarnpkg/fslib");
7
7
  const parsers_1 = require("@yarnpkg/parsers");
8
8
  const ci_info_1 = tslib_1.__importDefault(require("ci-info"));
9
9
  const clipanion_1 = require("clipanion");
10
+ const semver_1 = tslib_1.__importDefault(require("semver"));
10
11
  const t = tslib_1.__importStar(require("typanion"));
12
+ const LOCKFILE_MIGRATION_RULES = [{
13
+ selector: v => v === -1,
14
+ name: `nodeLinker`,
15
+ value: `node-modules`,
16
+ }, {
17
+ selector: v => v !== -1 && v < 8,
18
+ name: `enableGlobalCache`,
19
+ value: false,
20
+ }, {
21
+ selector: v => v !== -1 && v < 8,
22
+ name: `compressionLevel`,
23
+ value: `mixed`,
24
+ }];
11
25
  // eslint-disable-next-line arca/no-default-export
12
26
  class YarnCommand extends cli_1.BaseCommand {
13
27
  constructor() {
@@ -49,7 +63,6 @@ class YarnCommand extends cli_1.BaseCommand {
49
63
  this.networkTimeout = clipanion_1.Option.String(`--network-timeout`, { hidden: true });
50
64
  }
51
65
  async execute() {
52
- var _a, _b, _c;
53
66
  const configuration = await core_1.Configuration.find(this.context.cwd, this.context.plugins);
54
67
  if (typeof this.inlineBuilds !== `undefined`)
55
68
  configuration.useWithSource(`<cli>`, { enableInlineBuilds: this.inlineBuilds }, configuration.startingCwd, { overwrite: true });
@@ -163,7 +176,7 @@ class YarnCommand extends cli_1.BaseCommand {
163
176
  const updateMode = this.mode === core_1.InstallMode.UpdateLockfile;
164
177
  if (updateMode && (this.immutable || this.immutableCache))
165
178
  throw new clipanion_1.UsageError(`${core_1.formatUtils.pretty(configuration, `--immutable`, core_1.formatUtils.Type.CODE)} and ${core_1.formatUtils.pretty(configuration, `--immutable-cache`, core_1.formatUtils.Type.CODE)} cannot be used with ${core_1.formatUtils.pretty(configuration, `--mode=update-lockfile`, core_1.formatUtils.Type.CODE)}`);
166
- const immutable = ((_a = this.immutable) !== null && _a !== void 0 ? _a : configuration.get(`enableImmutableInstalls`)) && !updateMode;
179
+ const immutable = (this.immutable ?? configuration.get(`enableImmutableInstalls`)) && !updateMode;
167
180
  const immutableCache = this.immutableCache && !updateMode;
168
181
  if (configuration.projectCwd !== null) {
169
182
  const fixReport = await core_1.StreamReport.start({
@@ -172,8 +185,16 @@ class YarnCommand extends cli_1.BaseCommand {
172
185
  stdout: this.context.stdout,
173
186
  includeFooter: false,
174
187
  }, async (report) => {
188
+ let changed = false;
189
+ if (await autofixLegacyPlugins(configuration, immutable)) {
190
+ report.reportInfo(core_1.MessageName.AUTOMERGE_SUCCESS, `Automatically removed core plugins that are now builtins 👍`);
191
+ changed = true;
192
+ }
175
193
  if (await autofixMergeConflicts(configuration, immutable)) {
176
194
  report.reportInfo(core_1.MessageName.AUTOMERGE_SUCCESS, `Automatically fixed merge conflicts 👍`);
195
+ changed = true;
196
+ }
197
+ if (changed) {
177
198
  report.reportSeparator();
178
199
  }
179
200
  });
@@ -181,34 +202,6 @@ class YarnCommand extends cli_1.BaseCommand {
181
202
  return fixReport.exitCode();
182
203
  }
183
204
  }
184
- if (configuration.projectCwd !== null && typeof configuration.sources.get(`nodeLinker`) === `undefined`) {
185
- const projectCwd = configuration.projectCwd;
186
- let content;
187
- try {
188
- content = await fslib_1.xfs.readFilePromise(fslib_1.ppath.join(projectCwd, fslib_1.Filename.lockfile), `utf8`);
189
- }
190
- catch { }
191
- // If migrating from a v1 install, we automatically enable the node-modules linker,
192
- // since that's likely what the author intended to do.
193
- if (content === null || content === void 0 ? void 0 : content.includes(`yarn lockfile v1`)) {
194
- const nmReport = await core_1.StreamReport.start({
195
- configuration,
196
- json: this.json,
197
- stdout: this.context.stdout,
198
- includeFooter: false,
199
- }, async (report) => {
200
- report.reportInfo(core_1.MessageName.AUTO_NM_SUCCESS, `Migrating from Yarn 1; automatically enabling the compatibility node-modules linker 👍`);
201
- report.reportSeparator();
202
- configuration.use(`<compat>`, { nodeLinker: `node-modules` }, projectCwd, { overwrite: true });
203
- await core_1.Configuration.updateConfiguration(projectCwd, {
204
- nodeLinker: `node-modules`,
205
- });
206
- });
207
- if (nmReport.hasErrors()) {
208
- return nmReport.exitCode();
209
- }
210
- }
211
- }
212
205
  if (configuration.projectCwd !== null) {
213
206
  const telemetryReport = await core_1.StreamReport.start({
214
207
  configuration,
@@ -216,18 +209,72 @@ class YarnCommand extends cli_1.BaseCommand {
216
209
  stdout: this.context.stdout,
217
210
  includeFooter: false,
218
211
  }, async (report) => {
219
- var _a;
220
- if ((_a = core_1.Configuration.telemetry) === null || _a === void 0 ? void 0 : _a.isNew) {
212
+ if (core_1.Configuration.telemetry?.isNew) {
213
+ core_1.Configuration.telemetry.commitTips();
221
214
  report.reportInfo(core_1.MessageName.TELEMETRY_NOTICE, `Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry`);
222
215
  report.reportInfo(core_1.MessageName.TELEMETRY_NOTICE, `Run ${core_1.formatUtils.pretty(configuration, `yarn config set --home enableTelemetry 0`, core_1.formatUtils.Type.CODE)} to disable`);
223
216
  report.reportSeparator();
224
217
  }
218
+ else if (core_1.Configuration.telemetry?.shouldShowTips) {
219
+ const data = await core_1.httpUtils.get(`https://repo.yarnpkg.com/tags`, { configuration, jsonResponse: true }).catch(() => null);
220
+ if (data !== null) {
221
+ let newVersion = null;
222
+ if (core_1.YarnVersion !== null) {
223
+ const isRcBinary = semver_1.default.prerelease(core_1.YarnVersion);
224
+ const releaseType = isRcBinary ? `canary` : `stable`;
225
+ const candidate = data.latest[releaseType];
226
+ if (semver_1.default.gt(candidate, core_1.YarnVersion)) {
227
+ newVersion = [releaseType, candidate];
228
+ }
229
+ }
230
+ if (newVersion) {
231
+ core_1.Configuration.telemetry.commitTips();
232
+ report.reportInfo(core_1.MessageName.VERSION_NOTICE, `${core_1.formatUtils.applyStyle(configuration, `A new ${newVersion[0]} version of Yarn is available:`, core_1.formatUtils.Style.BOLD)} ${core_1.structUtils.prettyReference(configuration, newVersion[1])}!`);
233
+ report.reportInfo(core_1.MessageName.VERSION_NOTICE, `Upgrade now by running ${core_1.formatUtils.pretty(configuration, `yarn set version ${newVersion[1]}`, core_1.formatUtils.Type.CODE)}`);
234
+ report.reportSeparator();
235
+ }
236
+ else {
237
+ const tip = core_1.Configuration.telemetry.selectTip(data.tips);
238
+ if (tip) {
239
+ report.reportInfo(core_1.MessageName.TIPS_NOTICE, core_1.formatUtils.pretty(configuration, tip.message, core_1.formatUtils.Type.MARKDOWN_INLINE));
240
+ if (tip.url)
241
+ report.reportInfo(core_1.MessageName.TIPS_NOTICE, `Learn more at ${tip.url}`);
242
+ report.reportSeparator();
243
+ }
244
+ }
245
+ }
246
+ }
225
247
  });
226
248
  if (telemetryReport.hasErrors()) {
227
249
  return telemetryReport.exitCode();
228
250
  }
229
251
  }
230
252
  const { project, workspace } = await core_1.Project.find(configuration, this.context.cwd);
253
+ const lockfileLastVersion = project.lockfileLastVersion;
254
+ if (lockfileLastVersion !== null) {
255
+ const compatReport = await core_1.StreamReport.start({
256
+ configuration,
257
+ json: this.json,
258
+ stdout: this.context.stdout,
259
+ includeFooter: false,
260
+ }, async (report) => {
261
+ const newSettings = {};
262
+ for (const rule of LOCKFILE_MIGRATION_RULES) {
263
+ if (rule.selector(lockfileLastVersion) && typeof configuration.sources.get(rule.name) === `undefined`) {
264
+ configuration.use(`<compat>`, { [rule.name]: rule.value }, project.cwd, { overwrite: true });
265
+ newSettings[rule.name] = rule.value;
266
+ }
267
+ }
268
+ if (Object.keys(newSettings).length > 0) {
269
+ await core_1.Configuration.updateConfiguration(project.cwd, newSettings);
270
+ report.reportInfo(core_1.MessageName.MIGRATION_SUCCESS, `Migrated your project to the latest Yarn version 🚀`);
271
+ report.reportSeparator();
272
+ }
273
+ });
274
+ if (compatReport.hasErrors()) {
275
+ return compatReport.exitCode();
276
+ }
277
+ }
231
278
  const cache = await core_1.Cache.find(configuration, { immutable: immutableCache, check: this.checkCache });
232
279
  if (!workspace)
233
280
  throw new cli_1.WorkspaceRequiredError(project.cwd, this.context.cwd);
@@ -235,9 +282,9 @@ class YarnCommand extends cli_1.BaseCommand {
235
282
  restoreResolutions: false,
236
283
  });
237
284
  const enableHardenedMode = configuration.get(`enableHardenedMode`);
238
- if ((_b = this.refreshLockfile) !== null && _b !== void 0 ? _b : enableHardenedMode)
285
+ if (this.refreshLockfile ?? enableHardenedMode)
239
286
  project.lockfileNeedsRefresh = true;
240
- const checkResolutions = (_c = this.checkResolutions) !== null && _c !== void 0 ? _c : enableHardenedMode;
287
+ const checkResolutions = this.checkResolutions ?? enableHardenedMode;
241
288
  // Important: Because other commands also need to run installs, if you
242
289
  // get in a situation where you need to change this file in order to
243
290
  // customize the install it's very likely you're doing something wrong.
@@ -245,18 +292,17 @@ class YarnCommand extends cli_1.BaseCommand {
245
292
  // install logic should be implemented elsewhere (probably in either of
246
293
  // the Configuration and Install classes). Feel free to open an issue
247
294
  // in order to ask for design feedback before writing features.
248
- const report = await core_1.StreamReport.start({
249
- configuration,
295
+ return await project.installWithNewReport({
250
296
  json: this.json,
251
297
  stdout: this.context.stdout,
252
- includeLogs: true,
253
- }, async (report) => {
254
- await project.install({ cache, report, immutable, checkResolutions, mode: this.mode });
298
+ }, {
299
+ cache,
300
+ immutable,
301
+ checkResolutions,
302
+ mode: this.mode,
255
303
  });
256
- return report.exitCode();
257
304
  }
258
305
  }
259
- exports.default = YarnCommand;
260
306
  YarnCommand.paths = [
261
307
  [`install`],
262
308
  clipanion_1.Command.Default,
@@ -288,7 +334,7 @@ YarnCommand.usage = clipanion_1.Command.Usage({
288
334
 
289
335
  If the \`--mode=<mode>\` option is set, Yarn will change which artifacts are generated. The modes currently supported are:
290
336
 
291
- - \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the later will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.
337
+ - \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.
292
338
 
293
339
  - \`update-lockfile\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.
294
340
  `,
@@ -303,14 +349,12 @@ YarnCommand.usage = clipanion_1.Command.Usage({
303
349
  `$0 install --immutable --immutable-cache --check-cache`,
304
350
  ]],
305
351
  });
306
- const MERGE_CONFLICT_ANCESTOR = `|||||||`;
307
- const MERGE_CONFLICT_END = `>>>>>>>`;
308
- const MERGE_CONFLICT_SEP = `=======`;
352
+ exports.default = YarnCommand;
309
353
  const MERGE_CONFLICT_START = `<<<<<<<`;
310
354
  async function autofixMergeConflicts(configuration, immutable) {
311
355
  if (!configuration.projectCwd)
312
356
  return false;
313
- const lockfilePath = fslib_1.ppath.join(configuration.projectCwd, configuration.get(`lockfileFilename`));
357
+ const lockfilePath = fslib_1.ppath.join(configuration.projectCwd, fslib_1.Filename.lockfile);
314
358
  if (!await fslib_1.xfs.existsPromise(lockfilePath))
315
359
  return false;
316
360
  const file = await fslib_1.xfs.readFilePromise(lockfilePath, `utf8`);
@@ -318,22 +362,74 @@ async function autofixMergeConflicts(configuration, immutable) {
318
362
  return false;
319
363
  if (immutable)
320
364
  throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_IMMUTABLE, `Cannot autofix a lockfile when running an immutable install`);
321
- const [left, right] = getVariants(file);
322
- let parsedLeft;
323
- let parsedRight;
324
- try {
325
- parsedLeft = (0, parsers_1.parseSyml)(left);
326
- parsedRight = (0, parsers_1.parseSyml)(right);
365
+ let commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `MERGE_HEAD`, `HEAD`], {
366
+ cwd: configuration.projectCwd,
367
+ });
368
+ if (commits.code !== 0) {
369
+ commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `REBASE_HEAD`, `HEAD`], {
370
+ cwd: configuration.projectCwd,
371
+ });
327
372
  }
328
- catch (error) {
329
- throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_FAILED_TO_PARSE, `The individual variants of the lockfile failed to parse`);
373
+ if (commits.code !== 0) {
374
+ commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `CHERRY_PICK_HEAD`, `HEAD`], {
375
+ cwd: configuration.projectCwd,
376
+ });
330
377
  }
331
- const merged = {
332
- ...parsedLeft,
333
- ...parsedRight,
334
- };
378
+ if (commits.code !== 0)
379
+ throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_GIT_ERROR, `Git returned an error when trying to find the commits pertaining to the conflict`);
380
+ let variants = await Promise.all(commits.stdout.trim().split(/\n/).map(async (hash) => {
381
+ const content = await core_1.execUtils.execvp(`git`, [`show`, `${hash}:./${fslib_1.Filename.lockfile}`], {
382
+ cwd: configuration.projectCwd,
383
+ });
384
+ if (content.code !== 0)
385
+ throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_GIT_ERROR, `Git returned an error when trying to access the lockfile content in ${hash}`);
386
+ try {
387
+ return (0, parsers_1.parseSyml)(content.stdout);
388
+ }
389
+ catch {
390
+ throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_FAILED_TO_PARSE, `A variant of the conflicting lockfile failed to parse`);
391
+ }
392
+ }));
335
393
  // Old-style lockfiles should be filtered out (for example when switching
336
- // from a Yarn 2 branch to a Yarn 1 branch). Fortunately (?), they actually
394
+ // from a Yarn 2 branch to a Yarn 1 branch).
395
+ variants = variants.filter(variant => {
396
+ return !!variant.__metadata;
397
+ });
398
+ for (const variant of variants) {
399
+ // Pre-lockfile v7, the entries weren't normalized (ie we had "foo@x.y.z"
400
+ // in the lockfile rather than "foo@npm:x.y.z")
401
+ if (variant.__metadata.version < 7) {
402
+ for (const key of Object.keys(variant)) {
403
+ if (key === `__metadata`)
404
+ continue;
405
+ const descriptor = core_1.structUtils.parseDescriptor(key, true);
406
+ const normalizedDescriptor = configuration.normalizeDependency(descriptor);
407
+ const newKey = core_1.structUtils.stringifyDescriptor(normalizedDescriptor);
408
+ if (newKey !== key) {
409
+ variant[newKey] = variant[key];
410
+ delete variant[key];
411
+ }
412
+ }
413
+ }
414
+ // We encode the cacheKeys inside the checksums so that the reconciliation
415
+ // can merge the data together
416
+ for (const key of Object.keys(variant)) {
417
+ if (key === `__metadata`)
418
+ continue;
419
+ const checksum = variant[key].checksum;
420
+ if (typeof checksum === `string` && checksum.includes(`/`))
421
+ continue;
422
+ variant[key].checksum = `${variant.__metadata.cacheKey}/${checksum}`;
423
+ }
424
+ }
425
+ const merged = Object.assign({}, ...variants);
426
+ // We must keep the lockfile version as small as necessary to force Yarn to
427
+ // refresh the merged-in lockfile metadata that may be missing.
428
+ merged.__metadata.version = `${Math.min(...variants.map(variant => {
429
+ return parseInt(variant.__metadata.version ?? 0);
430
+ }))}`;
431
+ // It shouldn't matter, since the cacheKey have been embed within the checksums
432
+ merged.__metadata.cacheKey = `merged`;
337
433
  // parse as valid YAML except that the objects become strings. We can use
338
434
  // that to detect them. Damn, it's really ugly though.
339
435
  for (const [key, value] of Object.entries(merged))
@@ -344,52 +440,37 @@ async function autofixMergeConflicts(configuration, immutable) {
344
440
  });
345
441
  return true;
346
442
  }
347
- function getVariants(file) {
348
- const variants = [[], []];
349
- const lines = file.split(/\r?\n/g);
350
- let skip = false;
351
- while (lines.length > 0) {
352
- const line = lines.shift();
353
- if (typeof line === `undefined`)
354
- throw new Error(`Assertion failed: Some lines should remain`);
355
- if (line.startsWith(MERGE_CONFLICT_START)) {
356
- // get the first variant
357
- while (lines.length > 0) {
358
- const conflictLine = lines.shift();
359
- if (typeof conflictLine === `undefined`)
360
- throw new Error(`Assertion failed: Some lines should remain`);
361
- if (conflictLine === MERGE_CONFLICT_SEP) {
362
- skip = false;
363
- break;
364
- }
365
- else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
366
- skip = true;
367
- continue;
368
- }
369
- else {
370
- variants[0].push(conflictLine);
371
- }
372
- }
373
- // get the second variant
374
- while (lines.length > 0) {
375
- const conflictLine = lines.shift();
376
- if (typeof conflictLine === `undefined`)
377
- throw new Error(`Assertion failed: Some lines should remain`);
378
- if (conflictLine.startsWith(MERGE_CONFLICT_END)) {
379
- break;
380
- }
381
- else {
382
- variants[1].push(conflictLine);
383
- }
384
- }
385
- }
386
- else {
387
- variants[0].push(line);
388
- variants[1].push(line);
389
- }
390
- }
391
- return [
392
- variants[0].join(`\n`),
393
- variants[1].join(`\n`),
394
- ];
443
+ async function autofixLegacyPlugins(configuration, immutable) {
444
+ if (!configuration.projectCwd)
445
+ return false;
446
+ const legacyPlugins = [];
447
+ const yarnPluginDir = fslib_1.ppath.join(configuration.projectCwd, `.yarn/plugins/@yarnpkg`);
448
+ const changed = await core_1.Configuration.updateConfiguration(configuration.projectCwd, {
449
+ plugins: plugins => {
450
+ if (!Array.isArray(plugins))
451
+ return plugins;
452
+ const filteredPlugins = plugins.filter((plugin) => {
453
+ if (!plugin.path)
454
+ return true;
455
+ const resolvedPath = fslib_1.ppath.resolve(configuration.projectCwd, plugin.path);
456
+ const isLegacy = core_1.LEGACY_PLUGINS.has(plugin.spec) && fslib_1.ppath.contains(yarnPluginDir, resolvedPath);
457
+ if (isLegacy)
458
+ legacyPlugins.push(resolvedPath);
459
+ return !isLegacy;
460
+ });
461
+ if (filteredPlugins.length === 0)
462
+ return core_1.Configuration.deleteProperty;
463
+ if (filteredPlugins.length === plugins.length)
464
+ return plugins;
465
+ return filteredPlugins;
466
+ },
467
+ }, {
468
+ immutable,
469
+ });
470
+ if (!changed)
471
+ return false;
472
+ await Promise.all(legacyPlugins.map(async (pluginPath) => {
473
+ await fslib_1.xfs.removePromise(pluginPath);
474
+ }));
475
+ return true;
395
476
  }
@@ -6,6 +6,6 @@ export default class LinkCommand extends BaseCommand {
6
6
  all: boolean;
7
7
  private: boolean;
8
8
  relative: boolean;
9
- destination: string;
10
- execute(): Promise<1 | 0>;
9
+ destinations: string[];
10
+ execute(): Promise<0 | 1>;
11
11
  }
@@ -9,15 +9,15 @@ class LinkCommand extends cli_1.BaseCommand {
9
9
  constructor() {
10
10
  super(...arguments);
11
11
  this.all = clipanion_1.Option.Boolean(`-A,--all`, false, {
12
- description: `Link all workspaces belonging to the target project to the current one`,
12
+ description: `Link all workspaces belonging to the target projects to the current one`,
13
13
  });
14
14
  this.private = clipanion_1.Option.Boolean(`-p,--private`, false, {
15
- description: `Also link private workspaces belonging to the target project to the current one`,
15
+ description: `Also link private workspaces belonging to the target projects to the current one`,
16
16
  });
17
17
  this.relative = clipanion_1.Option.Boolean(`-r,--relative`, false, {
18
18
  description: `Link workspaces using relative paths instead of absolute paths`,
19
19
  });
20
- this.destination = clipanion_1.Option.String();
20
+ this.destinations = clipanion_1.Option.Rest();
21
21
  }
22
22
  async execute() {
23
23
  const configuration = await core_1.Configuration.find(this.context.cwd, this.context.plugins);
@@ -28,32 +28,38 @@ class LinkCommand extends cli_1.BaseCommand {
28
28
  await project.restoreInstallState({
29
29
  restoreResolutions: false,
30
30
  });
31
- const absoluteDestination = fslib_1.ppath.resolve(this.context.cwd, fslib_1.npath.toPortablePath(this.destination));
32
- const configuration2 = await core_1.Configuration.find(absoluteDestination, this.context.plugins, { useRc: false, strict: false });
33
- const { project: project2, workspace: workspace2 } = await core_1.Project.find(configuration2, absoluteDestination);
34
- if (project.cwd === project2.cwd)
35
- throw new clipanion_1.UsageError(`Invalid destination; Can't link the project to itself`);
36
- if (!workspace2)
37
- throw new cli_1.WorkspaceRequiredError(project2.cwd, absoluteDestination);
38
31
  const topLevelWorkspace = project.topLevelWorkspace;
39
32
  const linkedWorkspaces = [];
40
- if (this.all) {
41
- for (const workspace of project2.workspaces)
42
- if (workspace.manifest.name && (!workspace.manifest.private || this.private))
43
- linkedWorkspaces.push(workspace);
44
- if (linkedWorkspaces.length === 0) {
45
- throw new clipanion_1.UsageError(`No workspace found to be linked in the target project`);
33
+ for (const destination of this.destinations) {
34
+ const absoluteDestination = fslib_1.ppath.resolve(this.context.cwd, fslib_1.npath.toPortablePath(destination));
35
+ const configuration2 = await core_1.Configuration.find(absoluteDestination, this.context.plugins, { useRc: false, strict: false });
36
+ const { project: project2, workspace: workspace2 } = await core_1.Project.find(configuration2, absoluteDestination);
37
+ if (project.cwd === project2.cwd)
38
+ throw new clipanion_1.UsageError(`Invalid destination '${destination}'; Can't link the project to itself`);
39
+ if (!workspace2)
40
+ throw new cli_1.WorkspaceRequiredError(project2.cwd, absoluteDestination);
41
+ if (this.all) {
42
+ let found = false;
43
+ for (const workspace of project2.workspaces) {
44
+ if (workspace.manifest.name && (!workspace.manifest.private || this.private)) {
45
+ linkedWorkspaces.push(workspace);
46
+ found = true;
47
+ }
48
+ }
49
+ if (!found) {
50
+ throw new clipanion_1.UsageError(`No workspace found to be linked in the target project: ${destination}`);
51
+ }
52
+ }
53
+ else {
54
+ if (!workspace2.manifest.name)
55
+ throw new clipanion_1.UsageError(`The target workspace at '${destination}' doesn't have a name and thus cannot be linked`);
56
+ if (workspace2.manifest.private && !this.private)
57
+ throw new clipanion_1.UsageError(`The target workspace at '${destination}' is marked private - use the --private flag to link it anyway`);
58
+ linkedWorkspaces.push(workspace2);
46
59
  }
47
- }
48
- else {
49
- if (!workspace2.manifest.name)
50
- throw new clipanion_1.UsageError(`The target workspace doesn't have a name and thus cannot be linked`);
51
- if (workspace2.manifest.private && !this.private)
52
- throw new clipanion_1.UsageError(`The target workspace is marked private - use the --private flag to link it anyway`);
53
- linkedWorkspaces.push(workspace2);
54
60
  }
55
61
  for (const workspace of linkedWorkspaces) {
56
- const fullName = core_1.structUtils.stringifyIdent(workspace.locator);
62
+ const fullName = core_1.structUtils.stringifyIdent(workspace.anchoredLocator);
57
63
  const target = this.relative
58
64
  ? fslib_1.ppath.relative(project.cwd, workspace.cwd)
59
65
  : workspace.cwd;
@@ -62,16 +68,13 @@ class LinkCommand extends cli_1.BaseCommand {
62
68
  reference: `portal:${target}`,
63
69
  });
64
70
  }
65
- const report = await core_1.StreamReport.start({
66
- configuration,
71
+ return await project.installWithNewReport({
67
72
  stdout: this.context.stdout,
68
- }, async (report) => {
69
- await project.install({ cache, report });
73
+ }, {
74
+ cache,
70
75
  });
71
- return report.exitCode();
72
76
  }
73
77
  }
74
- exports.default = LinkCommand;
75
78
  LinkCommand.paths = [
76
79
  [`link`],
77
80
  ];
@@ -81,10 +84,11 @@ LinkCommand.usage = clipanion_1.Command.Usage({
81
84
  This command will set a new \`resolutions\` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).
82
85
  `,
83
86
  examples: [[
84
- `Register a remote workspace for use in the current project`,
85
- `$0 link ~/ts-loader`,
87
+ `Register one or more remote workspaces for use in the current project`,
88
+ `$0 link ~/ts-loader ~/jest`,
86
89
  ], [
87
90
  `Register all workspaces from a remote project for use in the current project`,
88
91
  `$0 link ~/jest --all`,
89
92
  ]],
90
93
  });
94
+ exports.default = LinkCommand;
@@ -12,7 +12,6 @@ class NodeCommand extends cli_1.BaseCommand {
12
12
  return this.cli.run([`exec`, `node`, ...this.args]);
13
13
  }
14
14
  }
15
- exports.default = NodeCommand;
16
15
  NodeCommand.paths = [
17
16
  [`node`],
18
17
  ];
@@ -28,3 +27,4 @@ NodeCommand.usage = clipanion_1.Command.Usage({
28
27
  `$0 node ./my-script.js`,
29
28
  ]],
30
29
  });
30
+ exports.default = NodeCommand;
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '@yarnpkg/cli';
2
+ import { Usage } from 'clipanion';
3
+ export default class PluginCheckCommand extends BaseCommand {
4
+ static paths: string[][];
5
+ static usage: Usage;
6
+ json: boolean;
7
+ execute(): Promise<0 | 1>;
8
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const cli_1 = require("@yarnpkg/cli");
4
+ const core_1 = require("@yarnpkg/core");
5
+ const clipanion_1 = require("clipanion");
6
+ // eslint-disable-next-line arca/no-default-export
7
+ class PluginCheckCommand extends cli_1.BaseCommand {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.json = clipanion_1.Option.Boolean(`--json`, false, {
11
+ description: `Format the output as an NDJSON stream`,
12
+ });
13
+ }
14
+ async execute() {
15
+ const configuration = await core_1.Configuration.find(this.context.cwd, this.context.plugins);
16
+ const rcFiles = await core_1.Configuration.findRcFiles(this.context.cwd);
17
+ const report = await core_1.StreamReport.start({
18
+ configuration,
19
+ json: this.json,
20
+ stdout: this.context.stdout,
21
+ }, async (report) => {
22
+ for (const rcFile of rcFiles) {
23
+ if (!rcFile.data?.plugins)
24
+ continue;
25
+ for (const plugin of rcFile.data.plugins) {
26
+ if (!plugin.checksum)
27
+ continue;
28
+ if (!plugin.spec.match(/^https?:/))
29
+ continue;
30
+ const newBuffer = await core_1.httpUtils.get(plugin.spec, { configuration });
31
+ const newChecksum = core_1.hashUtils.makeHash(newBuffer);
32
+ if (plugin.checksum === newChecksum)
33
+ continue;
34
+ const prettyPath = core_1.formatUtils.pretty(configuration, plugin.path, core_1.formatUtils.Type.PATH);
35
+ const prettySpec = core_1.formatUtils.pretty(configuration, plugin.spec, core_1.formatUtils.Type.URL);
36
+ const prettyMessage = `${prettyPath} is different from the file provided by ${prettySpec}`;
37
+ report.reportJson({ ...plugin, newChecksum });
38
+ report.reportError(core_1.MessageName.UNNAMED, prettyMessage);
39
+ }
40
+ }
41
+ });
42
+ return report.exitCode();
43
+ }
44
+ }
45
+ PluginCheckCommand.paths = [
46
+ [`plugin`, `check`],
47
+ ];
48
+ PluginCheckCommand.usage = clipanion_1.Command.Usage({
49
+ category: `Plugin-related commands`,
50
+ description: `find all third-party plugins that differ from their own spec`,
51
+ details: `
52
+ Check only the plugins from https.
53
+
54
+ If this command detects any plugin differences in the CI environment, it will throw an error.
55
+ `,
56
+ examples: [[
57
+ `find all third-party plugins that differ from their own spec`,
58
+ `$0 plugin check`,
59
+ ]],
60
+ });
61
+ exports.default = PluginCheckCommand;
@@ -3,7 +3,7 @@ import { Report, CommandContext } from '@yarnpkg/core';
3
3
  import { Project } from '@yarnpkg/core';
4
4
  import { PortablePath } from '@yarnpkg/fslib';
5
5
  import { Usage } from 'clipanion';
6
- export default class PluginDlSourcesCommand extends BaseCommand {
6
+ export default class PluginImportSourcesCommand extends BaseCommand {
7
7
  static paths: string[][];
8
8
  static usage: Usage;
9
9
  installPath: string | undefined;
@@ -12,9 +12,9 @@ export default class PluginDlSourcesCommand extends BaseCommand {
12
12
  noMinify: boolean;
13
13
  force: boolean;
14
14
  name: string;
15
- execute(): Promise<1 | 0>;
15
+ execute(): Promise<0 | 1>;
16
16
  }
17
- export declare type BuildAndSavePluginsSpec = {
17
+ export type BuildAndSavePluginsSpec = {
18
18
  context: CommandContext;
19
19
  noMinify: boolean;
20
20
  };