@yarnpkg/plugin-essentials 4.0.0-rc.9 → 4.0.1

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 +2 -1
  8. package/lib/commands/config/get.js +4 -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 +5 -3
  14. package/lib/commands/config.js +96 -39
  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 +3 -3
  22. package/lib/commands/explain/peerRequirements.js +75 -106
  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 +245 -205
  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 +22 -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 });
@@ -57,113 +70,72 @@ class YarnCommand extends cli_1.BaseCommand {
57
70
  // in process of deploying Google Cloud Functions and
58
71
  // Google App Engine
59
72
  const isGCP = !!process.env.FUNCTION_TARGET || !!process.env.GOOGLE_RUNTIME;
60
- const reportDeprecation = async (message, { error }) => {
61
- const deprecationReport = await core_1.StreamReport.start({
62
- configuration,
63
- stdout: this.context.stdout,
64
- includeFooter: false,
65
- }, async (report) => {
66
- if (error) {
67
- report.reportError(core_1.MessageName.DEPRECATED_CLI_SETTINGS, message);
68
- }
69
- else {
70
- report.reportWarning(core_1.MessageName.DEPRECATED_CLI_SETTINGS, message);
71
- }
72
- });
73
- if (deprecationReport.hasErrors()) {
74
- return deprecationReport.exitCode();
75
- }
76
- else {
77
- return null;
78
- }
79
- };
80
- // The ignoreEngines flag isn't implemented at the moment. I'm still
81
- // considering how it should work in the context of plugins - would it
82
- // make sense to allow them (or direct dependencies) to define new
83
- // "engine check"? Since it has implications regarding the architecture,
84
- // I prefer to postpone the decision to later. Also it wouldn't be a flag,
85
- // it would definitely be a configuration setting.
86
- if (typeof this.ignoreEngines !== `undefined`) {
87
- const exitCode = await reportDeprecation(`The --ignore-engines option is deprecated; engine checking isn't a core feature anymore`, {
73
+ const deprecationExitCode = await (0, core_1.reportOptionDeprecations)({
74
+ configuration,
75
+ stdout: this.context.stdout,
76
+ }, [{
77
+ // The ignoreEngines flag isn't implemented at the moment. I'm still
78
+ // considering how it should work in the context of plugins - would it
79
+ // make sense to allow them (or direct dependencies) to define new
80
+ // "engine check"? Since it has implications regarding the architecture,
81
+ // I prefer to postpone the decision to later. Also it wouldn't be a flag,
82
+ // it would definitely be a configuration setting.
83
+ option: this.ignoreEngines,
84
+ message: `The --ignore-engines option is deprecated; engine checking isn't a core feature anymore`,
88
85
  error: !ci_info_1.default.VERCEL,
89
- });
90
- if (exitCode !== null) {
91
- return exitCode;
92
- }
93
- }
94
- // The registry flag isn't supported anymore because it makes little sense
95
- // to use a registry for a single install. You instead want to configure it
96
- // for all installs inside a project, so through the .yarnrc.yml file. Note
97
- // that if absolutely necessary, the old behavior can be emulated by adding
98
- // the YARN_NPM_REGISTRY_SERVER variable to the environment.
99
- if (typeof this.registry !== `undefined`) {
100
- const exitCode = await reportDeprecation(`The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file`, {
101
- error: false,
102
- });
103
- if (exitCode !== null) {
104
- return exitCode;
105
- }
106
- }
107
- // The preferOffline flag doesn't make much sense with our architecture.
108
- // It would require the fetchers to also act as resolvers, which is
109
- // doable but quirky. Since a similar behavior is available via the
110
- // --cached flag in yarn add, I prefer to move it outside of the core and
111
- // let someone implement this "resolver-that-reads-the-cache" logic.
112
- if (typeof this.preferOffline !== `undefined`) {
113
- const exitCode = await reportDeprecation(`The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead`, {
86
+ }, {
87
+ // The registry flag isn't supported anymore because it makes little sense
88
+ // to use a registry for a single install. You instead want to configure it
89
+ // for all installs inside a project, so through the .yarnrc.yml file. Note
90
+ // that if absolutely necessary, the old behavior can be emulated by adding
91
+ // the YARN_NPM_REGISTRY_SERVER variable to the environment.
92
+ option: this.registry,
93
+ message: `The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file`,
94
+ }, {
95
+ // The preferOffline flag doesn't make much sense with our architecture.
96
+ // It would require the fetchers to also act as resolvers, which is
97
+ // doable but quirky. Since a similar behavior is available via the
98
+ // --cached flag in yarn add, I prefer to move it outside of the core and
99
+ // let someone implement this "resolver-that-reads-the-cache" logic.
100
+ option: this.preferOffline,
101
+ message: `The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead`,
114
102
  error: !ci_info_1.default.VERCEL,
115
- });
116
- if (exitCode !== null) {
117
- return exitCode;
118
- }
119
- }
120
- // Since the production flag would yield a different lockfile than the
121
- // regular installs, it's not part of the regular `install` command anymore.
122
- // Instead, we expect users to use it with `yarn workspaces focus` (which can
123
- // be used even outside of monorepos).
124
- if (typeof this.production !== `undefined`) {
125
- const exitCode = await reportDeprecation(`The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead`, {
103
+ }, {
104
+ // Since the production flag would yield a different lockfile than the
105
+ // regular installs, it's not part of the regular `install` command anymore.
106
+ // Instead, we expect users to use it with `yarn workspaces focus` (which can
107
+ // be used even outside of monorepos).
108
+ option: this.production,
109
+ message: `The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead`,
126
110
  error: true,
127
- });
128
- if (exitCode !== null) {
129
- return exitCode;
130
- }
131
- }
132
- // Yarn 2 isn't interactive during installs anyway, so there's no real point
133
- // to this flag at the moment.
134
- if (typeof this.nonInteractive !== `undefined`) {
135
- const exitCode = await reportDeprecation(`The --non-interactive option is deprecated`, {
111
+ }, {
112
+ // Yarn 2+ isn't interactive during installs anyway, so there's no real point
113
+ // to this flag at the moment.
114
+ option: this.nonInteractive,
115
+ message: `The --non-interactive option is deprecated`,
136
116
  error: !isGCP,
137
- });
138
- if (exitCode !== null) {
139
- return exitCode;
140
- }
141
- }
142
- // We want to prevent people from using --frozen-lockfile
143
- // Note: it's been deprecated because we're now locking more than just the
144
- // lockfile - for example the PnP artifacts will also be locked.
145
- if (typeof this.frozenLockfile !== `undefined`) {
146
- await reportDeprecation(`The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead`, {
147
- error: false,
148
- });
149
- this.immutable = this.frozenLockfile;
150
- }
151
- // We also want to prevent them from using --cache-folder
152
- // Note: it's been deprecated because the cache folder should be set from
153
- // the settings. Otherwise there would be a very high chance that multiple
154
- // Yarn commands would use different caches, causing unexpected behaviors.
155
- if (typeof this.cacheFolder !== `undefined`) {
156
- const exitCode = await reportDeprecation(`The cache-folder option has been deprecated; use rc settings instead`, {
117
+ }, {
118
+ // We want to prevent people from using --frozen-lockfile
119
+ // Note: it's been deprecated because we're now locking more than just the
120
+ // lockfile - for example the PnP artifacts will also be locked.
121
+ option: this.frozenLockfile,
122
+ message: `The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead`,
123
+ callback: () => this.immutable = this.frozenLockfile,
124
+ }, {
125
+ // We also want to prevent them from using --cache-folder
126
+ // Note: it's been deprecated because the cache folder should be set from
127
+ // the settings. Otherwise there would be a very high chance that multiple
128
+ // Yarn commands would use different caches, causing unexpected behaviors.
129
+ option: this.cacheFolder,
130
+ message: `The cache-folder option has been deprecated; use rc settings instead`,
157
131
  error: !ci_info_1.default.NETLIFY,
158
- });
159
- if (exitCode !== null) {
160
- return exitCode;
161
- }
162
- }
132
+ }]);
133
+ if (deprecationExitCode !== null)
134
+ return deprecationExitCode;
163
135
  const updateMode = this.mode === core_1.InstallMode.UpdateLockfile;
164
136
  if (updateMode && (this.immutable || this.immutableCache))
165
137
  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;
138
+ const immutable = (this.immutable ?? configuration.get(`enableImmutableInstalls`)) && !updateMode;
167
139
  const immutableCache = this.immutableCache && !updateMode;
168
140
  if (configuration.projectCwd !== null) {
169
141
  const fixReport = await core_1.StreamReport.start({
@@ -172,8 +144,16 @@ class YarnCommand extends cli_1.BaseCommand {
172
144
  stdout: this.context.stdout,
173
145
  includeFooter: false,
174
146
  }, async (report) => {
147
+ let changed = false;
148
+ if (await autofixLegacyPlugins(configuration, immutable)) {
149
+ report.reportInfo(core_1.MessageName.AUTOMERGE_SUCCESS, `Automatically removed core plugins that are now builtins 👍`);
150
+ changed = true;
151
+ }
175
152
  if (await autofixMergeConflicts(configuration, immutable)) {
176
153
  report.reportInfo(core_1.MessageName.AUTOMERGE_SUCCESS, `Automatically fixed merge conflicts 👍`);
154
+ changed = true;
155
+ }
156
+ if (changed) {
177
157
  report.reportSeparator();
178
158
  }
179
159
  });
@@ -181,34 +161,6 @@ class YarnCommand extends cli_1.BaseCommand {
181
161
  return fixReport.exitCode();
182
162
  }
183
163
  }
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
164
  if (configuration.projectCwd !== null) {
213
165
  const telemetryReport = await core_1.StreamReport.start({
214
166
  configuration,
@@ -216,18 +168,72 @@ class YarnCommand extends cli_1.BaseCommand {
216
168
  stdout: this.context.stdout,
217
169
  includeFooter: false,
218
170
  }, async (report) => {
219
- var _a;
220
- if ((_a = core_1.Configuration.telemetry) === null || _a === void 0 ? void 0 : _a.isNew) {
171
+ if (core_1.Configuration.telemetry?.isNew) {
172
+ core_1.Configuration.telemetry.commitTips();
221
173
  report.reportInfo(core_1.MessageName.TELEMETRY_NOTICE, `Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry`);
222
174
  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
175
  report.reportSeparator();
224
176
  }
177
+ else if (core_1.Configuration.telemetry?.shouldShowTips) {
178
+ const data = await core_1.httpUtils.get(`https://repo.yarnpkg.com/tags`, { configuration, jsonResponse: true }).catch(() => null);
179
+ if (data !== null) {
180
+ let newVersion = null;
181
+ if (core_1.YarnVersion !== null) {
182
+ const isRcBinary = semver_1.default.prerelease(core_1.YarnVersion);
183
+ const releaseType = isRcBinary ? `canary` : `stable`;
184
+ const candidate = data.latest[releaseType];
185
+ if (semver_1.default.gt(candidate, core_1.YarnVersion)) {
186
+ newVersion = [releaseType, candidate];
187
+ }
188
+ }
189
+ if (newVersion) {
190
+ core_1.Configuration.telemetry.commitTips();
191
+ 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])}!`);
192
+ 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)}`);
193
+ report.reportSeparator();
194
+ }
195
+ else {
196
+ const tip = core_1.Configuration.telemetry.selectTip(data.tips);
197
+ if (tip) {
198
+ report.reportInfo(core_1.MessageName.TIPS_NOTICE, core_1.formatUtils.pretty(configuration, tip.message, core_1.formatUtils.Type.MARKDOWN_INLINE));
199
+ if (tip.url)
200
+ report.reportInfo(core_1.MessageName.TIPS_NOTICE, `Learn more at ${tip.url}`);
201
+ report.reportSeparator();
202
+ }
203
+ }
204
+ }
205
+ }
225
206
  });
226
207
  if (telemetryReport.hasErrors()) {
227
208
  return telemetryReport.exitCode();
228
209
  }
229
210
  }
230
211
  const { project, workspace } = await core_1.Project.find(configuration, this.context.cwd);
212
+ const lockfileLastVersion = project.lockfileLastVersion;
213
+ if (lockfileLastVersion !== null) {
214
+ const compatReport = await core_1.StreamReport.start({
215
+ configuration,
216
+ json: this.json,
217
+ stdout: this.context.stdout,
218
+ includeFooter: false,
219
+ }, async (report) => {
220
+ const newSettings = {};
221
+ for (const rule of LOCKFILE_MIGRATION_RULES) {
222
+ if (rule.selector(lockfileLastVersion) && typeof configuration.sources.get(rule.name) === `undefined`) {
223
+ configuration.use(`<compat>`, { [rule.name]: rule.value }, project.cwd, { overwrite: true });
224
+ newSettings[rule.name] = rule.value;
225
+ }
226
+ }
227
+ if (Object.keys(newSettings).length > 0) {
228
+ await core_1.Configuration.updateConfiguration(project.cwd, newSettings);
229
+ report.reportInfo(core_1.MessageName.MIGRATION_SUCCESS, `Migrated your project to the latest Yarn version 🚀`);
230
+ report.reportSeparator();
231
+ }
232
+ });
233
+ if (compatReport.hasErrors()) {
234
+ return compatReport.exitCode();
235
+ }
236
+ }
231
237
  const cache = await core_1.Cache.find(configuration, { immutable: immutableCache, check: this.checkCache });
232
238
  if (!workspace)
233
239
  throw new cli_1.WorkspaceRequiredError(project.cwd, this.context.cwd);
@@ -235,9 +241,9 @@ class YarnCommand extends cli_1.BaseCommand {
235
241
  restoreResolutions: false,
236
242
  });
237
243
  const enableHardenedMode = configuration.get(`enableHardenedMode`);
238
- if ((_b = this.refreshLockfile) !== null && _b !== void 0 ? _b : enableHardenedMode)
244
+ if (this.refreshLockfile ?? enableHardenedMode)
239
245
  project.lockfileNeedsRefresh = true;
240
- const checkResolutions = (_c = this.checkResolutions) !== null && _c !== void 0 ? _c : enableHardenedMode;
246
+ const checkResolutions = this.checkResolutions ?? enableHardenedMode;
241
247
  // Important: Because other commands also need to run installs, if you
242
248
  // get in a situation where you need to change this file in order to
243
249
  // customize the install it's very likely you're doing something wrong.
@@ -245,18 +251,17 @@ class YarnCommand extends cli_1.BaseCommand {
245
251
  // install logic should be implemented elsewhere (probably in either of
246
252
  // the Configuration and Install classes). Feel free to open an issue
247
253
  // in order to ask for design feedback before writing features.
248
- const report = await core_1.StreamReport.start({
249
- configuration,
254
+ return await project.installWithNewReport({
250
255
  json: this.json,
251
256
  stdout: this.context.stdout,
252
- includeLogs: true,
253
- }, async (report) => {
254
- await project.install({ cache, report, immutable, checkResolutions, mode: this.mode });
257
+ }, {
258
+ cache,
259
+ immutable,
260
+ checkResolutions,
261
+ mode: this.mode,
255
262
  });
256
- return report.exitCode();
257
263
  }
258
264
  }
259
- exports.default = YarnCommand;
260
265
  YarnCommand.paths = [
261
266
  [`install`],
262
267
  clipanion_1.Command.Default,
@@ -288,7 +293,7 @@ YarnCommand.usage = clipanion_1.Command.Usage({
288
293
 
289
294
  If the \`--mode=<mode>\` option is set, Yarn will change which artifacts are generated. The modes currently supported are:
290
295
 
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.
296
+ - \`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
297
 
293
298
  - \`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
299
  `,
@@ -303,14 +308,12 @@ YarnCommand.usage = clipanion_1.Command.Usage({
303
308
  `$0 install --immutable --immutable-cache --check-cache`,
304
309
  ]],
305
310
  });
306
- const MERGE_CONFLICT_ANCESTOR = `|||||||`;
307
- const MERGE_CONFLICT_END = `>>>>>>>`;
308
- const MERGE_CONFLICT_SEP = `=======`;
311
+ exports.default = YarnCommand;
309
312
  const MERGE_CONFLICT_START = `<<<<<<<`;
310
313
  async function autofixMergeConflicts(configuration, immutable) {
311
314
  if (!configuration.projectCwd)
312
315
  return false;
313
- const lockfilePath = fslib_1.ppath.join(configuration.projectCwd, configuration.get(`lockfileFilename`));
316
+ const lockfilePath = fslib_1.ppath.join(configuration.projectCwd, fslib_1.Filename.lockfile);
314
317
  if (!await fslib_1.xfs.existsPromise(lockfilePath))
315
318
  return false;
316
319
  const file = await fslib_1.xfs.readFilePromise(lockfilePath, `utf8`);
@@ -318,22 +321,74 @@ async function autofixMergeConflicts(configuration, immutable) {
318
321
  return false;
319
322
  if (immutable)
320
323
  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);
324
+ let commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `MERGE_HEAD`, `HEAD`], {
325
+ cwd: configuration.projectCwd,
326
+ });
327
+ if (commits.code !== 0) {
328
+ commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `REBASE_HEAD`, `HEAD`], {
329
+ cwd: configuration.projectCwd,
330
+ });
327
331
  }
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`);
332
+ if (commits.code !== 0) {
333
+ commits = await core_1.execUtils.execvp(`git`, [`rev-parse`, `CHERRY_PICK_HEAD`, `HEAD`], {
334
+ cwd: configuration.projectCwd,
335
+ });
330
336
  }
331
- const merged = {
332
- ...parsedLeft,
333
- ...parsedRight,
334
- };
337
+ if (commits.code !== 0)
338
+ 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`);
339
+ let variants = await Promise.all(commits.stdout.trim().split(/\n/).map(async (hash) => {
340
+ const content = await core_1.execUtils.execvp(`git`, [`show`, `${hash}:./${fslib_1.Filename.lockfile}`], {
341
+ cwd: configuration.projectCwd,
342
+ });
343
+ if (content.code !== 0)
344
+ throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_GIT_ERROR, `Git returned an error when trying to access the lockfile content in ${hash}`);
345
+ try {
346
+ return (0, parsers_1.parseSyml)(content.stdout);
347
+ }
348
+ catch {
349
+ throw new core_1.ReportError(core_1.MessageName.AUTOMERGE_FAILED_TO_PARSE, `A variant of the conflicting lockfile failed to parse`);
350
+ }
351
+ }));
335
352
  // 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
353
+ // from a Yarn 2 branch to a Yarn 1 branch).
354
+ variants = variants.filter(variant => {
355
+ return !!variant.__metadata;
356
+ });
357
+ for (const variant of variants) {
358
+ // Pre-lockfile v7, the entries weren't normalized (ie we had "foo@x.y.z"
359
+ // in the lockfile rather than "foo@npm:x.y.z")
360
+ if (variant.__metadata.version < 7) {
361
+ for (const key of Object.keys(variant)) {
362
+ if (key === `__metadata`)
363
+ continue;
364
+ const descriptor = core_1.structUtils.parseDescriptor(key, true);
365
+ const normalizedDescriptor = configuration.normalizeDependency(descriptor);
366
+ const newKey = core_1.structUtils.stringifyDescriptor(normalizedDescriptor);
367
+ if (newKey !== key) {
368
+ variant[newKey] = variant[key];
369
+ delete variant[key];
370
+ }
371
+ }
372
+ }
373
+ // We encode the cacheKeys inside the checksums so that the reconciliation
374
+ // can merge the data together
375
+ for (const key of Object.keys(variant)) {
376
+ if (key === `__metadata`)
377
+ continue;
378
+ const checksum = variant[key].checksum;
379
+ if (typeof checksum === `string` && checksum.includes(`/`))
380
+ continue;
381
+ variant[key].checksum = `${variant.__metadata.cacheKey}/${checksum}`;
382
+ }
383
+ }
384
+ const merged = Object.assign({}, ...variants);
385
+ // We must keep the lockfile version as small as necessary to force Yarn to
386
+ // refresh the merged-in lockfile metadata that may be missing.
387
+ merged.__metadata.version = `${Math.min(...variants.map(variant => {
388
+ return parseInt(variant.__metadata.version ?? 0);
389
+ }))}`;
390
+ // It shouldn't matter, since the cacheKey have been embed within the checksums
391
+ merged.__metadata.cacheKey = `merged`;
337
392
  // parse as valid YAML except that the objects become strings. We can use
338
393
  // that to detect them. Damn, it's really ugly though.
339
394
  for (const [key, value] of Object.entries(merged))
@@ -344,52 +399,37 @@ async function autofixMergeConflicts(configuration, immutable) {
344
399
  });
345
400
  return true;
346
401
  }
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
- ];
402
+ async function autofixLegacyPlugins(configuration, immutable) {
403
+ if (!configuration.projectCwd)
404
+ return false;
405
+ const legacyPlugins = [];
406
+ const yarnPluginDir = fslib_1.ppath.join(configuration.projectCwd, `.yarn/plugins/@yarnpkg`);
407
+ const changed = await core_1.Configuration.updateConfiguration(configuration.projectCwd, {
408
+ plugins: plugins => {
409
+ if (!Array.isArray(plugins))
410
+ return plugins;
411
+ const filteredPlugins = plugins.filter((plugin) => {
412
+ if (!plugin.path)
413
+ return true;
414
+ const resolvedPath = fslib_1.ppath.resolve(configuration.projectCwd, plugin.path);
415
+ const isLegacy = core_1.LEGACY_PLUGINS.has(plugin.spec) && fslib_1.ppath.contains(yarnPluginDir, resolvedPath);
416
+ if (isLegacy)
417
+ legacyPlugins.push(resolvedPath);
418
+ return !isLegacy;
419
+ });
420
+ if (filteredPlugins.length === 0)
421
+ return core_1.Configuration.deleteProperty;
422
+ if (filteredPlugins.length === plugins.length)
423
+ return plugins;
424
+ return filteredPlugins;
425
+ },
426
+ }, {
427
+ immutable,
428
+ });
429
+ if (!changed)
430
+ return false;
431
+ await Promise.all(legacyPlugins.map(async (pluginPath) => {
432
+ await fslib_1.xfs.removePromise(pluginPath);
433
+ }));
434
+ return true;
395
435
  }
@@ -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
  }