@yarnpkg/plugin-essentials 3.2.0 → 4.0.0-rc.3

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.
@@ -5,6 +5,7 @@ export default class AddCommand extends BaseCommand {
5
5
  static paths: string[][];
6
6
  static usage: Usage;
7
7
  json: boolean;
8
+ fixed: boolean;
8
9
  exact: boolean;
9
10
  tilde: boolean;
10
11
  caret: boolean;
@@ -16,6 +16,9 @@ class AddCommand extends cli_1.BaseCommand {
16
16
  this.json = clipanion_1.Option.Boolean(`--json`, false, {
17
17
  description: `Format the output as an NDJSON stream`,
18
18
  });
19
+ this.fixed = clipanion_1.Option.Boolean(`-F,--fixed`, false, {
20
+ description: `Store dependency tags as-is instead of resolving them`,
21
+ });
19
22
  this.exact = clipanion_1.Option.Boolean(`-E,--exact`, false, {
20
23
  description: `Don't use any semver modifier on the resolved range`,
21
24
  });
@@ -60,18 +63,20 @@ class AddCommand extends cli_1.BaseCommand {
60
63
  await project.restoreInstallState({
61
64
  restoreResolutions: false,
62
65
  });
66
+ const fixed = this.fixed;
63
67
  const interactive = (_a = this.interactive) !== null && _a !== void 0 ? _a : configuration.get(`preferInteractive`);
68
+ const reuse = interactive || configuration.get(`preferReuse`);
64
69
  const modifier = suggestUtils.getModifier(this, project);
65
70
  const strategies = [
66
- ...interactive ? [
67
- suggestUtils.Strategy.REUSE,
68
- ] : [],
71
+ reuse ?
72
+ suggestUtils.Strategy.REUSE
73
+ : undefined,
69
74
  suggestUtils.Strategy.PROJECT,
70
- ...this.cached ? [
71
- suggestUtils.Strategy.CACHE,
72
- ] : [],
75
+ this.cached ?
76
+ suggestUtils.Strategy.CACHE
77
+ : undefined,
73
78
  suggestUtils.Strategy.LATEST,
74
- ];
79
+ ].filter((strategy) => typeof strategy !== `undefined`);
75
80
  const maxResults = interactive
76
81
  ? Infinity
77
82
  : 1;
@@ -90,7 +95,7 @@ class AddCommand extends cli_1.BaseCommand {
90
95
  preferDev: this.preferDev,
91
96
  optional: this.optional,
92
97
  });
93
- const suggestions = await suggestUtils.getSuggestedDescriptors(request, { project, workspace, cache, target, modifier, strategies, maxResults });
98
+ const suggestions = await suggestUtils.getSuggestedDescriptors(request, { project, workspace, cache, fixed, target, modifier, strategies, maxResults });
94
99
  return [request, suggestions, target];
95
100
  }));
96
101
  const checkReport = await core_1.LightReport.start({
@@ -7,7 +7,9 @@ export default class YarnCommand extends BaseCommand {
7
7
  json: boolean;
8
8
  immutable: boolean | undefined;
9
9
  immutableCache: boolean | undefined;
10
- checkCache: boolean;
10
+ refreshLockfile: boolean | undefined;
11
+ checkCache: boolean | undefined;
12
+ checkResolutions: boolean | undefined;
11
13
  inlineBuilds: boolean | undefined;
12
14
  mode: InstallMode | undefined;
13
15
  cacheFolder: string | undefined;
@@ -21,9 +21,15 @@ class YarnCommand extends cli_1.BaseCommand {
21
21
  this.immutableCache = clipanion_1.Option.Boolean(`--immutable-cache`, {
22
22
  description: `Abort with an error exit code if the cache folder was to be modified`,
23
23
  });
24
- this.checkCache = clipanion_1.Option.Boolean(`--check-cache`, false, {
24
+ this.refreshLockfile = clipanion_1.Option.Boolean(`--refresh-lockfile`, {
25
+ description: `Refresh the package metadata stored in the lockfile`,
26
+ });
27
+ this.checkCache = clipanion_1.Option.Boolean(`--check-cache`, {
25
28
  description: `Always refetch the packages and ensure that their checksums are consistent`,
26
29
  });
30
+ this.checkResolutions = clipanion_1.Option.Boolean(`--check-resolutions`, {
31
+ description: `Validates that the package resolutions are coherent`,
32
+ });
27
33
  this.inlineBuilds = clipanion_1.Option.Boolean(`--inline-builds`, {
28
34
  description: `Verbosely print the output of the build steps of dependencies`,
29
35
  });
@@ -43,7 +49,7 @@ class YarnCommand extends cli_1.BaseCommand {
43
49
  this.networkTimeout = clipanion_1.Option.String(`--network-timeout`, { hidden: true });
44
50
  }
45
51
  async execute() {
46
- var _a;
52
+ var _a, _b, _c;
47
53
  const configuration = await core_1.Configuration.find(this.context.cwd, this.context.plugins);
48
54
  if (typeof this.inlineBuilds !== `undefined`)
49
55
  configuration.useWithSource(`<cli>`, { enableInlineBuilds: this.inlineBuilds }, configuration.startingCwd, { overwrite: true });
@@ -228,6 +234,10 @@ class YarnCommand extends cli_1.BaseCommand {
228
234
  await project.restoreInstallState({
229
235
  restoreResolutions: false,
230
236
  });
237
+ const enableHardenedMode = configuration.get(`enableHardenedMode`);
238
+ if ((_b = this.refreshLockfile) !== null && _b !== void 0 ? _b : enableHardenedMode)
239
+ project.lockfileNeedsRefresh = true;
240
+ const checkResolutions = (_c = this.checkResolutions) !== null && _c !== void 0 ? _c : enableHardenedMode;
231
241
  // Important: Because other commands also need to run installs, if you
232
242
  // get in a situation where you need to change this file in order to
233
243
  // customize the install it's very likely you're doing something wrong.
@@ -241,7 +251,7 @@ class YarnCommand extends cli_1.BaseCommand {
241
251
  stdout: this.context.stdout,
242
252
  includeLogs: true,
243
253
  }, async (report) => {
244
- await project.install({ cache, report, immutable, mode: this.mode });
254
+ await project.install({ cache, report, immutable, checkResolutions, mode: this.mode });
245
255
  });
246
256
  return report.exitCode();
247
257
  }
@@ -270,6 +280,8 @@ YarnCommand.usage = clipanion_1.Command.Usage({
270
280
 
271
281
  If the \`--immutable-cache\` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).
272
282
 
283
+ If the \`--refresh-lockfile\` option is set, Yarn will keep the same resolution for the packages currently in the lockfile but will refresh their metadata. If used together with \`--immutable\`, it can validate that the lockfile information are consistent. This flag is enabled by default when Yarn detects it runs within a pull request context.
284
+
273
285
  If the \`--check-cache\` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.
274
286
 
275
287
  If the \`--inline-builds\` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.
@@ -46,7 +46,7 @@ class PluginDlSourcesCommand extends cli_1.BaseCommand {
46
46
  const { project } = await core_2.Project.find(configuration, this.context.cwd);
47
47
  const ident = core_1.structUtils.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/, `@yarnpkg/plugin-`));
48
48
  const identStr = core_1.structUtils.stringifyIdent(ident);
49
- const data = await (0, list_1.getAvailablePlugins)(configuration);
49
+ const data = await (0, list_1.getAvailablePlugins)(configuration, core_1.YarnVersion);
50
50
  if (!Object.prototype.hasOwnProperty.call(data, identStr))
51
51
  throw new core_2.ReportError(core_2.MessageName.PLUGIN_NAME_NOT_FOUND, `Couldn't find a plugin named "${identStr}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);
52
52
  const pluginSpec = identStr;
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { BaseCommand } from '@yarnpkg/cli';
3
4
  import { Project, Report } from '@yarnpkg/core';
4
5
  import { Usage } from 'clipanion';
@@ -49,9 +49,15 @@ class PluginDlCommand extends cli_1.BaseCommand {
49
49
  if (locator.reference !== `unknown` && !semver_1.default.valid(locator.reference))
50
50
  throw new core_1.ReportError(core_1.MessageName.UNNAMED, `Official plugins only accept strict version references. Use an explicit URL if you wish to download them from another location.`);
51
51
  const identStr = core_2.structUtils.stringifyIdent(locator);
52
- const data = await (0, list_1.getAvailablePlugins)(configuration);
53
- if (!Object.prototype.hasOwnProperty.call(data, identStr))
54
- throw new core_1.ReportError(core_1.MessageName.PLUGIN_NAME_NOT_FOUND, `Couldn't find a plugin named "${identStr}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be referenced by their name; any other plugin will have to be referenced through its public url (for example https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js).`);
52
+ const data = await (0, list_1.getAvailablePlugins)(configuration, core_2.YarnVersion);
53
+ if (!Object.prototype.hasOwnProperty.call(data, identStr)) {
54
+ let message = `Couldn't find a plugin named ${core_2.structUtils.prettyIdent(configuration, locator)} on the remote registry.\n`;
55
+ if (configuration.plugins.has(identStr))
56
+ message += `A plugin named ${core_2.structUtils.prettyIdent(configuration, locator)} is already installed; possibly attempting to import a built-in plugin.`;
57
+ else
58
+ message += `Note that only the plugins referenced on our website (${core_2.formatUtils.pretty(configuration, `https://github.com/yarnpkg/berry/blob/master/plugins.yml`, core_2.formatUtils.Type.URL)}) can be referenced by their name; any other plugin will have to be referenced through its public url (for example ${core_2.formatUtils.pretty(configuration, `https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js`, core_2.formatUtils.Type.URL)}).`;
59
+ throw new core_1.ReportError(core_1.MessageName.PLUGIN_NAME_NOT_FOUND, message);
60
+ }
55
61
  pluginSpec = identStr;
56
62
  pluginUrl = data[identStr].url;
57
63
  if (locator.reference !== `unknown`) {
@@ -1,8 +1,8 @@
1
1
  import { BaseCommand } from '@yarnpkg/cli';
2
2
  import { Configuration } from '@yarnpkg/core';
3
3
  import { Usage } from 'clipanion';
4
- export declare function getAvailablePlugins(configuration: Configuration): Promise<{
5
- [key: string]: any;
4
+ export declare function getAvailablePlugins(configuration: Configuration, version: string | null): Promise<{
5
+ [k: string]: any;
6
6
  }>;
7
7
  export default class PluginDlCommand extends BaseCommand {
8
8
  static paths: string[][];
@@ -6,10 +6,13 @@ const core_1 = require("@yarnpkg/core");
6
6
  const parsers_1 = require("@yarnpkg/parsers");
7
7
  const clipanion_1 = require("clipanion");
8
8
  const REMOTE_REGISTRY = `https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml`;
9
- async function getAvailablePlugins(configuration) {
9
+ async function getAvailablePlugins(configuration, version) {
10
10
  const raw = await core_1.httpUtils.get(REMOTE_REGISTRY, { configuration });
11
11
  const data = (0, parsers_1.parseSyml)(raw.toString());
12
- return data;
12
+ return Object.fromEntries(Object.entries(data).filter(([pluginName, pluginData]) => {
13
+ var _a;
14
+ return !version || core_1.semverUtils.satisfiesWithPrereleases(version, (_a = pluginData.range) !== null && _a !== void 0 ? _a : `<4.0.0-rc.1`);
15
+ }));
13
16
  }
14
17
  exports.getAvailablePlugins = getAvailablePlugins;
15
18
  // eslint-disable-next-line arca/no-default-export
@@ -27,7 +30,7 @@ class PluginDlCommand extends cli_1.BaseCommand {
27
30
  json: this.json,
28
31
  stdout: this.context.stdout,
29
32
  }, async (report) => {
30
- const data = await getAvailablePlugins(configuration);
33
+ const data = await getAvailablePlugins(configuration, core_1.YarnVersion);
31
34
  for (const [name, { experimental, ...rest }] of Object.entries(data)) {
32
35
  let label = name;
33
36
  if (experimental)
@@ -7,6 +7,7 @@ export default class RunCommand extends BaseCommand {
7
7
  inspectBrk: string | boolean;
8
8
  topLevel: boolean;
9
9
  binariesOnly: boolean;
10
+ require: string | undefined;
10
11
  silent: boolean | undefined;
11
12
  scriptName: string;
12
13
  args: string[];
@@ -22,6 +22,9 @@ class RunCommand extends cli_1.BaseCommand {
22
22
  this.binariesOnly = clipanion_1.Option.Boolean(`-B,--binaries-only`, false, {
23
23
  description: `Ignore any user defined scripts and only check for binaries`,
24
24
  });
25
+ this.require = clipanion_1.Option.String(`--require`, {
26
+ description: `Forwarded to the underlying Node process when executing a binary`,
27
+ });
25
28
  // The v1 used to print the Yarn version header when using "yarn run", which
26
29
  // was messing with the output of things like `--version` & co. We don't do
27
30
  // this anymore, but many workflows use `yarn run --silent` to make sure that
@@ -64,6 +67,8 @@ class RunCommand extends cli_1.BaseCommand {
64
67
  nodeArgs.push(`--inspect-brk`);
65
68
  }
66
69
  }
70
+ if (this.require)
71
+ nodeArgs.push(`--require=${this.require}`);
67
72
  return await core_2.scriptUtils.executePackageAccessibleBinary(effectiveLocator, this.scriptName, this.args, {
68
73
  cwd: this.context.cwd,
69
74
  project,
@@ -76,11 +76,11 @@ class SetVersionSourcesCommand extends cli_1.BaseCommand {
76
76
  report.reportSeparator();
77
77
  const bundlePath = fslib_1.ppath.resolve(target, `packages/yarnpkg-cli/bundles/yarn.js`);
78
78
  const bundleBuffer = await fslib_1.xfs.readFilePromise(bundlePath);
79
- await (0, version_1.setVersion)(configuration, `sources`, bundleBuffer, {
79
+ const { bundleVersion } = await (0, version_1.setVersion)(configuration, null, async () => bundleBuffer, {
80
80
  report,
81
81
  });
82
82
  if (!this.skipPlugins) {
83
- await updatePlugins(this, { project, report, target });
83
+ await updatePlugins(this, bundleVersion, { project, report, target });
84
84
  }
85
85
  });
86
86
  return report.exitCode();
@@ -155,8 +155,8 @@ async function prepareRepo(spec, { configuration, report, target }) {
155
155
  }
156
156
  }
157
157
  exports.prepareRepo = prepareRepo;
158
- async function updatePlugins(context, { project, report, target }) {
159
- const data = await (0, list_1.getAvailablePlugins)(project.configuration);
158
+ async function updatePlugins(context, version, { project, report, target }) {
159
+ const data = await (0, list_1.getAvailablePlugins)(project.configuration, version);
160
160
  const contribPlugins = new Set(Object.keys(data));
161
161
  for (const name of project.configuration.plugins.keys()) {
162
162
  if (!contribPlugins.has(name))
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { BaseCommand } from '@yarnpkg/cli';
3
4
  import { Configuration, Report } from '@yarnpkg/core';
4
5
  import { Usage } from 'clipanion';
@@ -9,12 +10,16 @@ export declare type Tags = {
9
10
  export default class SetVersionCommand extends BaseCommand {
10
11
  static paths: string[][];
11
12
  static usage: Usage;
13
+ useYarnPath: boolean | undefined;
12
14
  onlyIfNeeded: boolean;
13
15
  version: string;
14
16
  execute(): Promise<1 | 0>;
15
17
  }
16
18
  export declare function resolveRange(configuration: Configuration, request: string): Promise<string>;
17
19
  export declare function resolveTag(configuration: Configuration, request: `stable` | `canary`): Promise<string>;
18
- export declare function setVersion(configuration: Configuration, bundleVersion: string | null, bundleBuffer: Buffer, { report }: {
20
+ export declare function setVersion(configuration: Configuration, bundleVersion: string | null, fetchBuffer: () => Promise<Buffer>, { report, useYarnPath }: {
19
21
  report: Report;
20
- }): Promise<void>;
22
+ useYarnPath?: boolean;
23
+ }): Promise<{
24
+ bundleVersion: string;
25
+ }>;
@@ -12,6 +12,9 @@ const semver_1 = tslib_1.__importDefault(require("semver"));
12
12
  class SetVersionCommand extends cli_1.BaseCommand {
13
13
  constructor() {
14
14
  super(...arguments);
15
+ this.useYarnPath = clipanion_1.Option.Boolean(`--yarn-path`, {
16
+ description: `Set the yarnPath setting even if the version can be accessed by Corepack`,
17
+ });
15
18
  this.onlyIfNeeded = clipanion_1.Option.Boolean(`--only-if-needed`, false, {
16
19
  description: `Only lock the Yarn version if it isn't already locked`,
17
20
  });
@@ -26,25 +29,28 @@ class SetVersionCommand extends cli_1.BaseCommand {
26
29
  throw new clipanion_1.UsageError(`The --install flag can only be used without explicit version specifier from the Yarn CLI`);
27
30
  return `file://${process.argv[1]}`;
28
31
  };
29
- let bundleUrl;
32
+ let bundleRef;
33
+ const getRef = (url, version) => {
34
+ return { version, url: url.replace(/\{\}/g, version) };
35
+ };
30
36
  if (this.version === `self`)
31
- bundleUrl = getBundlePath();
37
+ bundleRef = { url: getBundlePath(), version: core_1.YarnVersion !== null && core_1.YarnVersion !== void 0 ? core_1.YarnVersion : `self` };
32
38
  else if (this.version === `latest` || this.version === `berry` || this.version === `stable`)
33
- bundleUrl = `https://repo.yarnpkg.com/${await resolveTag(configuration, `stable`)}/packages/yarnpkg-cli/bin/yarn.js`;
39
+ bundleRef = getRef(`https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js`, await resolveTag(configuration, `stable`));
34
40
  else if (this.version === `canary`)
35
- bundleUrl = `https://repo.yarnpkg.com/${await resolveTag(configuration, `canary`)}/packages/yarnpkg-cli/bin/yarn.js`;
41
+ bundleRef = getRef(`https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js`, await resolveTag(configuration, `canary`));
36
42
  else if (this.version === `classic`)
37
- bundleUrl = `https://nightly.yarnpkg.com/latest.js`;
43
+ bundleRef = { url: `https://nightly.yarnpkg.com/latest.js`, version: `classic` };
38
44
  else if (this.version.match(/^https?:/))
39
- bundleUrl = this.version;
45
+ bundleRef = { url: this.version, version: `remote` };
40
46
  else if (this.version.match(/^\.{0,2}[\\/]/) || fslib_1.npath.isAbsolute(this.version))
41
- bundleUrl = `file://${fslib_1.npath.resolve(this.version)}`;
47
+ bundleRef = { url: `file://${fslib_1.ppath.resolve(fslib_1.npath.toPortablePath(this.version))}`, version: `file` };
42
48
  else if (core_2.semverUtils.satisfiesWithPrereleases(this.version, `>=2.0.0`))
43
- bundleUrl = `https://repo.yarnpkg.com/${this.version}/packages/yarnpkg-cli/bin/yarn.js`;
49
+ bundleRef = getRef(`https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js`, this.version);
44
50
  else if (core_2.semverUtils.satisfiesWithPrereleases(this.version, `^0.x || ^1.x`))
45
- bundleUrl = `https://github.com/yarnpkg/yarn/releases/download/v${this.version}/yarn-${this.version}.js`;
51
+ bundleRef = getRef(`https://github.com/yarnpkg/yarn/releases/download/v{}/yarn-{}.js`, this.version);
46
52
  else if (core_2.semverUtils.validRange(this.version))
47
- bundleUrl = `https://repo.yarnpkg.com/${await resolveRange(configuration, this.version)}/packages/yarnpkg-cli/bin/yarn.js`;
53
+ bundleRef = getRef(`https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js`, await resolveRange(configuration, this.version));
48
54
  else
49
55
  throw new clipanion_1.UsageError(`Invalid version descriptor "${this.version}"`);
50
56
  const report = await core_1.StreamReport.start({
@@ -52,17 +58,18 @@ class SetVersionCommand extends cli_1.BaseCommand {
52
58
  stdout: this.context.stdout,
53
59
  includeLogs: !this.context.quiet,
54
60
  }, async (report) => {
55
- const filePrefix = `file://`;
56
- let bundleBuffer;
57
- if (bundleUrl.startsWith(filePrefix)) {
58
- report.reportInfo(core_1.MessageName.UNNAMED, `Downloading ${core_2.formatUtils.pretty(configuration, bundleUrl, core_1.FormatType.URL)}`);
59
- bundleBuffer = await fslib_1.xfs.readFilePromise(fslib_1.npath.toPortablePath(bundleUrl.slice(filePrefix.length)));
60
- }
61
- else {
62
- report.reportInfo(core_1.MessageName.UNNAMED, `Retrieving ${core_2.formatUtils.pretty(configuration, bundleUrl, core_1.FormatType.PATH)}`);
63
- bundleBuffer = await core_2.httpUtils.get(bundleUrl, { configuration });
64
- }
65
- await setVersion(configuration, null, bundleBuffer, { report });
61
+ const fetchBuffer = async () => {
62
+ const filePrefix = `file://`;
63
+ if (bundleRef.url.startsWith(filePrefix)) {
64
+ report.reportInfo(core_1.MessageName.UNNAMED, `Retrieving ${core_2.formatUtils.pretty(configuration, bundleRef.url, core_1.FormatType.PATH)}`);
65
+ return await fslib_1.xfs.readFilePromise(bundleRef.url.slice(filePrefix.length));
66
+ }
67
+ else {
68
+ report.reportInfo(core_1.MessageName.UNNAMED, `Downloading ${core_2.formatUtils.pretty(configuration, bundleRef.url, core_1.FormatType.URL)}`);
69
+ return await core_2.httpUtils.get(bundleRef.url, { configuration });
70
+ }
71
+ };
72
+ await setVersion(configuration, bundleRef.version, fetchBuffer, { report, useYarnPath: this.useYarnPath });
66
73
  });
67
74
  return report.exitCode();
68
75
  }
@@ -74,7 +81,9 @@ SetVersionCommand.paths = [
74
81
  SetVersionCommand.usage = clipanion_1.Command.Usage({
75
82
  description: `lock the Yarn version used by the project`,
76
83
  details: `
77
- This command will download a specific release of Yarn directly from the Yarn GitHub repository, will store it inside your project, and will change the \`yarnPath\` settings from your project \`.yarnrc.yml\` file to point to the new file.
84
+ This command will set a specific release of Yarn to be used by Corepack: https://nodejs.org/api/corepack.html.
85
+
86
+ By default it only will set the \`packageManager\` field at the root of your project, but if the referenced release cannot be represented this way, if you already have \`yarnPath\` configured, or if you set the \`--yarn-path\` command line flag, then the release will also be downloaded from the Yarn GitHub repository, stored inside your project, and referenced via the \`yarnPath\` settings from your project \`.yarnrc.yml\` file.
78
87
 
79
88
  A very good use case for this command is to enforce the version of Yarn used by the any single member of your team inside a same project - by doing this you ensure that you have control on Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting a different behavior than you.
80
89
 
@@ -138,9 +147,16 @@ async function resolveTag(configuration, request) {
138
147
  return data.latest[request];
139
148
  }
140
149
  exports.resolveTag = resolveTag;
141
- async function setVersion(configuration, bundleVersion, bundleBuffer, { report }) {
150
+ async function setVersion(configuration, bundleVersion, fetchBuffer, { report, useYarnPath }) {
142
151
  var _a;
152
+ let bundleBuffer;
153
+ const ensureBuffer = async () => {
154
+ if (typeof bundleBuffer === `undefined`)
155
+ bundleBuffer = await fetchBuffer();
156
+ return bundleBuffer;
157
+ };
143
158
  if (bundleVersion === null) {
159
+ const bundleBuffer = await ensureBuffer();
144
160
  await fslib_1.xfs.mktempPromise(async (tmpDir) => {
145
161
  const temporaryPath = fslib_1.ppath.join(tmpDir, `yarn.cjs`);
146
162
  await fslib_1.xfs.writeFilePromise(temporaryPath, bundleBuffer);
@@ -158,29 +174,51 @@ async function setVersion(configuration, bundleVersion, bundleBuffer, { report }
158
174
  const releaseFolder = fslib_1.ppath.resolve(projectCwd, `.yarn/releases`);
159
175
  const absolutePath = fslib_1.ppath.resolve(releaseFolder, `yarn-${bundleVersion}.cjs`);
160
176
  const displayPath = fslib_1.ppath.relative(configuration.startingCwd, absolutePath);
161
- const projectPath = fslib_1.ppath.relative(projectCwd, absolutePath);
177
+ const isTaggedYarnVersion = core_2.miscUtils.isTaggedYarnVersion(bundleVersion);
162
178
  const yarnPath = configuration.get(`yarnPath`);
163
- const updateConfig = yarnPath === null || yarnPath.startsWith(`${releaseFolder}/`);
164
- report.reportInfo(core_1.MessageName.UNNAMED, `Saving the new release in ${core_2.formatUtils.pretty(configuration, displayPath, `magenta`)}`);
165
- await fslib_1.xfs.removePromise(fslib_1.ppath.dirname(absolutePath));
166
- await fslib_1.xfs.mkdirPromise(fslib_1.ppath.dirname(absolutePath), { recursive: true });
167
- await fslib_1.xfs.writeFilePromise(absolutePath, bundleBuffer, { mode: 0o755 });
168
- if (updateConfig) {
179
+ const absolutelyMustUseYarnPath = !isTaggedYarnVersion;
180
+ let probablyShouldUseYarnPath = absolutelyMustUseYarnPath || !!yarnPath || !!useYarnPath;
181
+ if (useYarnPath === false) {
182
+ if (absolutelyMustUseYarnPath)
183
+ throw new core_1.ReportError(core_1.MessageName.UNNAMED, `You explicitly opted out of yarnPath usage in your command line, but the version you specified cannot be represented by Corepack`);
184
+ probablyShouldUseYarnPath = false;
185
+ }
186
+ else if (!probablyShouldUseYarnPath && !process.env.COREPACK_ROOT) {
187
+ report.reportWarning(core_1.MessageName.UNNAMED, `You don't seem to have ${core_2.formatUtils.applyHyperlink(configuration, `Corepack`, `https://nodejs.org/api/corepack.html`)} enabled; we'll have to rely on ${core_2.formatUtils.applyHyperlink(configuration, `yarnPath`, `https://yarnpkg.com/configuration/yarnrc#yarnPath`)} instead`);
188
+ probablyShouldUseYarnPath = true;
189
+ }
190
+ if (probablyShouldUseYarnPath) {
191
+ const bundleBuffer = await ensureBuffer();
192
+ report.reportInfo(core_1.MessageName.UNNAMED, `Saving the new release in ${core_2.formatUtils.pretty(configuration, displayPath, `magenta`)}`);
193
+ await fslib_1.xfs.removePromise(fslib_1.ppath.dirname(absolutePath));
194
+ await fslib_1.xfs.mkdirPromise(fslib_1.ppath.dirname(absolutePath), { recursive: true });
195
+ await fslib_1.xfs.writeFilePromise(absolutePath, bundleBuffer, { mode: 0o755 });
196
+ if (!yarnPath || fslib_1.ppath.contains(releaseFolder, yarnPath)) {
197
+ await core_1.Configuration.updateConfiguration(projectCwd, {
198
+ yarnPath: fslib_1.ppath.relative(projectCwd, absolutePath),
199
+ });
200
+ }
201
+ }
202
+ else {
203
+ await fslib_1.xfs.removePromise(fslib_1.ppath.dirname(absolutePath));
169
204
  await core_1.Configuration.updateConfiguration(projectCwd, {
170
- yarnPath: projectPath,
171
- });
172
- const manifest = (await core_1.Manifest.tryFind(projectCwd)) || new core_1.Manifest();
173
- manifest.packageManager = `yarn@${bundleVersion && core_2.miscUtils.isTaggedYarnVersion(bundleVersion)
174
- ? bundleVersion
175
- // If the version isn't tagged, we use the latest stable version as the wrapper
176
- : await resolveTag(configuration, `stable`)}`;
177
- const data = {};
178
- manifest.exportTo(data);
179
- const path = fslib_1.ppath.join(projectCwd, core_1.Manifest.fileName);
180
- const content = `${JSON.stringify(data, null, manifest.indent)}\n`;
181
- await fslib_1.xfs.changeFilePromise(path, content, {
182
- automaticNewlines: true,
205
+ yarnPath: core_1.Configuration.deleteProperty,
183
206
  });
184
207
  }
208
+ const manifest = (await core_1.Manifest.tryFind(projectCwd)) || new core_1.Manifest();
209
+ manifest.packageManager = `yarn@${isTaggedYarnVersion
210
+ ? bundleVersion
211
+ // If the version isn't tagged, we use the latest stable version as the wrapper
212
+ : await resolveTag(configuration, `stable`)}`;
213
+ const data = {};
214
+ manifest.exportTo(data);
215
+ const path = fslib_1.ppath.join(projectCwd, core_1.Manifest.fileName);
216
+ const content = `${JSON.stringify(data, null, manifest.indent)}\n`;
217
+ await fslib_1.xfs.changeFilePromise(path, content, {
218
+ automaticNewlines: true,
219
+ });
220
+ return {
221
+ bundleVersion: bundleVersion,
222
+ };
185
223
  }
186
224
  exports.setVersion = setVersion;
@@ -6,6 +6,7 @@ export default class UpCommand extends BaseCommand {
6
6
  static paths: string[][];
7
7
  static usage: Usage;
8
8
  interactive: boolean | undefined;
9
+ fixed: boolean;
9
10
  exact: boolean;
10
11
  tilde: boolean;
11
12
  caret: boolean;
@@ -17,6 +17,9 @@ class UpCommand extends cli_1.BaseCommand {
17
17
  this.interactive = clipanion_1.Option.Boolean(`-i,--interactive`, {
18
18
  description: `Offer various choices, depending on the detected upgrade paths`,
19
19
  });
20
+ this.fixed = clipanion_1.Option.Boolean(`-F,--fixed`, false, {
21
+ description: `Store dependency tags as-is instead of resolving them`,
22
+ });
20
23
  this.exact = clipanion_1.Option.Boolean(`-E,--exact`, false, {
21
24
  description: `Don't use any semver modifier on the resolved range`,
22
25
  });
@@ -90,6 +93,7 @@ class UpCommand extends cli_1.BaseCommand {
90
93
  await project.restoreInstallState({
91
94
  restoreResolutions: false,
92
95
  });
96
+ const fixed = this.fixed;
93
97
  const interactive = (_a = this.interactive) !== null && _a !== void 0 ? _a : configuration.get(`preferInteractive`);
94
98
  const modifier = suggestUtils.getModifier(this, project);
95
99
  const strategies = interactive ? [
@@ -124,7 +128,7 @@ class UpCommand extends cli_1.BaseCommand {
124
128
  workspace,
125
129
  target,
126
130
  existingDescriptor,
127
- await suggestUtils.getSuggestedDescriptors(request, { project, workspace, cache, target, modifier, strategies }),
131
+ await suggestUtils.getSuggestedDescriptors(request, { project, workspace, cache, target, fixed, modifier, strategies }),
128
132
  ];
129
133
  }));
130
134
  isReferenced = true;
@@ -185,7 +189,7 @@ class UpCommand extends cli_1.BaseCommand {
185
189
  ({ answer: selected } = await (0, enquirer_1.prompt)({
186
190
  type: `select`,
187
191
  name: `answer`,
188
- message: `Which range to you want to use in ${core_1.structUtils.prettyWorkspace(configuration, workspace)} ❯ ${target}?`,
192
+ message: `Which range do you want to use in ${core_1.structUtils.prettyWorkspace(configuration, workspace)} ❯ ${target}?`,
189
193
  choices: suggestions.map(({ descriptor, name, reason }) => descriptor ? {
190
194
  name,
191
195
  hint: reason,
@@ -219,7 +223,8 @@ class UpCommand extends cli_1.BaseCommand {
219
223
  else {
220
224
  const resolver = configuration.makeResolver();
221
225
  const resolveOptions = { project, resolver };
222
- const bound = resolver.bindDescriptor(current, workspace.anchoredLocator, resolveOptions);
226
+ const normalizedDependency = configuration.normalizeDependency(current);
227
+ const bound = resolver.bindDescriptor(normalizedDependency, workspace.anchoredLocator, resolveOptions);
223
228
  project.forgetResolution(bound);
224
229
  }
225
230
  }
@@ -1,15 +1,17 @@
1
1
  import { Project, ResolveOptions, Resolver, Descriptor, Package, Report, Cache } from '@yarnpkg/core';
2
2
  import { Fetcher, FetchOptions } from '@yarnpkg/core';
3
+ export declare type PackageUpdate = {
4
+ descriptor: Descriptor;
5
+ currentPackage: Package;
6
+ updatedPackage: Package;
7
+ resolvedPackage: Package;
8
+ };
3
9
  export declare type Algorithm = (project: Project, patterns: Array<string>, opts: {
4
10
  resolver: Resolver;
5
11
  resolveOptions: ResolveOptions;
6
12
  fetcher: Fetcher;
7
13
  fetchOptions: FetchOptions;
8
- }) => Promise<Array<Promise<{
9
- descriptor: Descriptor;
10
- currentPackage: Package;
11
- updatedPackage: Package;
12
- } | null>>>;
14
+ }) => Promise<Array<Promise<PackageUpdate>>>;
13
15
  export declare enum Strategy {
14
16
  /**
15
17
  * This strategy dedupes a locator to the best candidate already installed in the project.
@@ -26,44 +26,75 @@ const DEDUPE_ALGORITHMS = {
26
26
  throw new Error(`Assertion failed: The descriptor (${descriptorHash}) should have been registered`);
27
27
  core_1.miscUtils.getSetWithDefault(locatorsByIdent, descriptor.identHash).add(locatorHash);
28
28
  }
29
- return Array.from(project.storedDescriptors.values(), async (descriptor) => {
30
- if (patterns.length && !micromatch_1.default.isMatch(core_2.structUtils.stringifyIdent(descriptor), patterns))
31
- return null;
29
+ const deferredMap = new Map(core_1.miscUtils.mapAndFilter(project.storedDescriptors.values(), descriptor => {
30
+ // We only care about resolutions that are stored in the lockfile
31
+ // (we shouldn't accidentally try deduping virtual packages)
32
+ if (core_2.structUtils.isVirtualDescriptor(descriptor))
33
+ return core_1.miscUtils.mapAndFilter.skip;
34
+ return [descriptor.descriptorHash, core_1.miscUtils.makeDeferred()];
35
+ }));
36
+ for (const descriptor of project.storedDescriptors.values()) {
37
+ const deferred = deferredMap.get(descriptor.descriptorHash);
38
+ if (typeof deferred === `undefined`)
39
+ throw new Error(`Assertion failed: The descriptor (${descriptor.descriptorHash}) should have been registered`);
32
40
  const currentResolution = project.storedResolutions.get(descriptor.descriptorHash);
33
41
  if (typeof currentResolution === `undefined`)
34
42
  throw new Error(`Assertion failed: The resolution (${descriptor.descriptorHash}) should have been registered`);
35
- // We only care about resolutions that are stored in the lockfile
36
- // (we shouldn't accidentally try deduping virtual packages)
37
43
  const currentPackage = project.originalPackages.get(currentResolution);
38
44
  if (typeof currentPackage === `undefined`)
39
- return null;
40
- // No need to try deduping packages that are not persisted,
41
- // they will be resolved again anyways
42
- if (!resolver.shouldPersistResolution(currentPackage, resolveOptions))
43
- return null;
44
- const locators = locatorsByIdent.get(descriptor.identHash);
45
- if (typeof locators === `undefined`)
46
- throw new Error(`Assertion failed: The resolutions (${descriptor.identHash}) should have been registered`);
47
- // No need to choose when there's only one possibility
48
- if (locators.size === 1)
49
- return null;
50
- const references = [...locators].map(locatorHash => {
51
- const pkg = project.originalPackages.get(locatorHash);
52
- if (typeof pkg === `undefined`)
53
- throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`);
54
- return pkg.reference;
45
+ throw new Error(`Assertion failed: The package (${currentResolution}) should have been registered`);
46
+ Promise.resolve().then(async () => {
47
+ var _a;
48
+ const dependencies = resolver.getResolutionDependencies(descriptor, resolveOptions);
49
+ const resolvedDependencies = Object.fromEntries(await core_1.miscUtils.allSettledSafe(Object.entries(dependencies).map(async ([dependencyName, dependency]) => {
50
+ const dependencyDeferred = deferredMap.get(dependency.descriptorHash);
51
+ if (typeof dependencyDeferred === `undefined`)
52
+ throw new Error(`Assertion failed: The descriptor (${dependency.descriptorHash}) should have been registered`);
53
+ const dedupeResult = await dependencyDeferred.promise;
54
+ if (!dedupeResult)
55
+ throw new Error(`Assertion failed: Expected the dependency to have been through the dedupe process itself`);
56
+ return [dependencyName, dedupeResult.updatedPackage];
57
+ })));
58
+ if (patterns.length && !micromatch_1.default.isMatch(core_2.structUtils.stringifyIdent(descriptor), patterns))
59
+ return currentPackage;
60
+ // No need to try deduping packages that are not persisted,
61
+ // they will be resolved again anyways
62
+ if (!resolver.shouldPersistResolution(currentPackage, resolveOptions))
63
+ return currentPackage;
64
+ const candidateHashes = locatorsByIdent.get(descriptor.identHash);
65
+ if (typeof candidateHashes === `undefined`)
66
+ throw new Error(`Assertion failed: The resolutions (${descriptor.identHash}) should have been registered`);
67
+ // No need to choose when there's only one possibility
68
+ if (candidateHashes.size === 1)
69
+ return currentPackage;
70
+ const candidates = [...candidateHashes].map(locatorHash => {
71
+ const pkg = project.originalPackages.get(locatorHash);
72
+ if (typeof pkg === `undefined`)
73
+ throw new Error(`Assertion failed: The package (${locatorHash}) should have been registered`);
74
+ return pkg;
75
+ });
76
+ const satisfying = await resolver.getSatisfying(descriptor, resolvedDependencies, candidates, resolveOptions);
77
+ const bestLocator = (_a = satisfying.locators) === null || _a === void 0 ? void 0 : _a[0];
78
+ if (typeof bestLocator === `undefined` || !satisfying.sorted)
79
+ return currentPackage;
80
+ const updatedPackage = project.originalPackages.get(bestLocator.locatorHash);
81
+ if (typeof updatedPackage === `undefined`)
82
+ throw new Error(`Assertion failed: The package (${bestLocator.locatorHash}) should have been registered`);
83
+ return updatedPackage;
84
+ }).then(async (updatedPackage) => {
85
+ const resolvedPackage = await project.preparePackage(updatedPackage, { resolver, resolveOptions });
86
+ deferred.resolve({
87
+ descriptor,
88
+ currentPackage,
89
+ updatedPackage,
90
+ resolvedPackage,
91
+ });
92
+ }).catch(error => {
93
+ deferred.reject(error);
55
94
  });
56
- const candidates = await resolver.getSatisfying(descriptor, references, resolveOptions);
57
- const bestCandidate = candidates === null || candidates === void 0 ? void 0 : candidates[0];
58
- if (typeof bestCandidate === `undefined`)
59
- return null;
60
- const updatedResolution = bestCandidate.locatorHash;
61
- const updatedPackage = project.originalPackages.get(updatedResolution);
62
- if (typeof updatedPackage === `undefined`)
63
- throw new Error(`Assertion failed: The package (${updatedResolution}) should have been registered`);
64
- if (updatedResolution === currentResolution)
65
- return null;
66
- return { descriptor, currentPackage, updatedPackage };
95
+ }
96
+ return [...deferredMap.values()].map(deferred => {
97
+ return deferred.promise;
67
98
  });
68
99
  },
69
100
  };
@@ -97,7 +128,7 @@ async function dedupe(project, { strategy, patterns, cache, report }) {
97
128
  let dedupedPackageCount = 0;
98
129
  await Promise.all(dedupePromises.map(dedupePromise => dedupePromise
99
130
  .then(dedupe => {
100
- if (dedupe === null)
131
+ if (dedupe === null || dedupe.currentPackage.locatorHash === dedupe.updatedPackage.locatorHash)
101
132
  return;
102
133
  dedupedPackageCount++;
103
134
  const { descriptor, currentPackage, updatedPackage } = dedupe;
package/lib/index.d.ts CHANGED
@@ -42,6 +42,7 @@ declare module '@yarnpkg/core' {
42
42
  interface ConfigurationValueMap {
43
43
  enableImmutableInstalls: boolean;
44
44
  defaultSemverRangePrefix: `^` | `~` | ``;
45
+ preferReuse: boolean;
45
46
  }
46
47
  }
47
48
  declare const plugin: Plugin;
package/lib/index.js CHANGED
@@ -57,6 +57,11 @@ const plugin = {
57
57
  values: [`^`, `~`, ``],
58
58
  default: suggestUtils.Modifier.CARET,
59
59
  },
60
+ preferReuse: {
61
+ description: `If true, \`yarn add\` will attempt to reuse the most common dependency range in other workspaces.`,
62
+ type: core_1.SettingsType.BOOLEAN,
63
+ default: false,
64
+ },
60
65
  },
61
66
  commands: [
62
67
  clean_1.default,
@@ -78,18 +78,25 @@ export declare function extractDescriptorFromPath(path: PortablePath, { cwd, wor
78
78
  cwd: PortablePath;
79
79
  workspace: Workspace;
80
80
  }): Promise<Descriptor>;
81
- export declare function getSuggestedDescriptors(request: Descriptor, { project, workspace, cache, target, modifier, strategies, maxResults }: {
81
+ export declare function getSuggestedDescriptors(request: Descriptor, { project, workspace, cache, target, fixed, modifier, strategies, maxResults }: {
82
82
  project: Project;
83
83
  workspace: Workspace;
84
84
  cache: Cache;
85
85
  target: Target;
86
+ fixed: boolean;
86
87
  modifier: Modifier;
87
88
  strategies: Array<Strategy>;
88
89
  maxResults?: number;
89
90
  }): Promise<Results>;
90
- export declare function fetchDescriptorFrom(ident: Ident, range: string, { project, cache, workspace, preserveModifier }: {
91
+ export declare type FetchDescriptorFromOptions = {
91
92
  project: Project;
92
93
  cache: Cache;
93
94
  workspace: Workspace;
95
+ } & ({
94
96
  preserveModifier?: boolean | string;
95
- }): Promise<Descriptor | null>;
97
+ modifier?: undefined;
98
+ } | {
99
+ preserveModifier?: undefined;
100
+ modifier: Modifier;
101
+ });
102
+ export declare function fetchDescriptorFrom(ident: Ident, range: string, { project, cache, workspace, preserveModifier, modifier }: FetchDescriptorFromOptions): Promise<Descriptor | null>;
@@ -165,10 +165,15 @@ async function extractDescriptorFromPath(path, { cwd, workspace }) {
165
165
  });
166
166
  }
167
167
  exports.extractDescriptorFromPath = extractDescriptorFromPath;
168
- async function getSuggestedDescriptors(request, { project, workspace, cache, target, modifier, strategies, maxResults = Infinity }) {
168
+ async function getSuggestedDescriptors(request, { project, workspace, cache, target, fixed, modifier, strategies, maxResults = Infinity }) {
169
169
  if (!(maxResults >= 0))
170
170
  throw new Error(`Invalid maxResults (${maxResults})`);
171
- if (request.range !== `unknown`) {
171
+ const [requestRange, requestTag] = request.range !== `unknown`
172
+ ? fixed || semver_1.default.validRange(request.range) || !request.range.match(/^[a-z0-9._-]+$/i)
173
+ ? [request.range, `latest`]
174
+ : [`unknown`, request.range]
175
+ : [`unknown`, `latest`];
176
+ if (requestRange !== `unknown`) {
172
177
  return {
173
178
  suggestions: [{
174
179
  descriptor: request,
@@ -265,14 +270,7 @@ async function getSuggestedDescriptors(request, { project, workspace, cache, tar
265
270
  case Strategy.LATEST:
266
271
  {
267
272
  await trySuggest(async () => {
268
- if (request.range !== `unknown`) {
269
- suggested.push({
270
- descriptor: request,
271
- name: `Use ${core_2.structUtils.prettyRange(project.configuration, request.range)}`,
272
- reason: `(explicit range requested)`,
273
- });
274
- }
275
- else if (target === Target.PEER) {
273
+ if (target === Target.PEER) {
276
274
  suggested.push({
277
275
  descriptor: core_2.structUtils.makeDescriptor(request, `*`),
278
276
  name: `Use *`,
@@ -287,9 +285,8 @@ async function getSuggestedDescriptors(request, { project, workspace, cache, tar
287
285
  });
288
286
  }
289
287
  else {
290
- let latest = await fetchDescriptorFrom(request, `latest`, { project, cache, workspace, preserveModifier: false });
288
+ const latest = await fetchDescriptorFrom(request, requestTag, { project, cache, workspace, modifier });
291
289
  if (latest) {
292
- latest = applyModifier(latest, modifier);
293
290
  suggested.push({
294
291
  descriptor: latest,
295
292
  name: `Use ${core_2.structUtils.prettyDescriptor(project.configuration, latest)}`,
@@ -308,8 +305,8 @@ async function getSuggestedDescriptors(request, { project, workspace, cache, tar
308
305
  };
309
306
  }
310
307
  exports.getSuggestedDescriptors = getSuggestedDescriptors;
311
- async function fetchDescriptorFrom(ident, range, { project, cache, workspace, preserveModifier = true }) {
312
- const latestDescriptor = core_2.structUtils.makeDescriptor(ident, range);
308
+ async function fetchDescriptorFrom(ident, range, { project, cache, workspace, preserveModifier = true, modifier }) {
309
+ const latestDescriptor = project.configuration.normalizeDependency(core_2.structUtils.makeDescriptor(ident, range));
313
310
  const report = new core_1.ThrowReport();
314
311
  const fetcher = project.configuration.makeFetcher();
315
312
  const resolver = project.configuration.makeResolver();
@@ -318,7 +315,7 @@ async function fetchDescriptorFrom(ident, range, { project, cache, workspace, pr
318
315
  // The descriptor has to be bound for the resolvers that need a parent locator. (e.g. FileResolver)
319
316
  // If we didn't bind it, `yarn add ./folder` wouldn't work.
320
317
  const boundDescriptor = resolver.bindDescriptor(latestDescriptor, workspace.anchoredLocator, resolveOptions);
321
- const candidateLocators = await resolver.getCandidates(boundDescriptor, new Map(), resolveOptions);
318
+ const candidateLocators = await resolver.getCandidates(boundDescriptor, {}, resolveOptions);
322
319
  if (candidateLocators.length === 0)
323
320
  return null;
324
321
  // Per the requirements exposed in Resolver.ts, the best is the first one
@@ -326,12 +323,32 @@ async function fetchDescriptorFrom(ident, range, { project, cache, workspace, pr
326
323
  let { protocol, source, params, selector } = core_2.structUtils.parseRange(core_2.structUtils.convertToManifestRange(bestLocator.reference));
327
324
  if (protocol === project.configuration.get(`defaultProtocol`))
328
325
  protocol = null;
329
- if (semver_1.default.valid(selector) && preserveModifier !== false) {
330
- const referenceRange = typeof preserveModifier === `string`
331
- ? preserveModifier
332
- : latestDescriptor.range;
333
- const modifier = extractRangeModifier(referenceRange, { project });
334
- selector = modifier + selector;
326
+ if (semver_1.default.valid(selector)) {
327
+ const rawSelector = selector;
328
+ if (typeof modifier !== `undefined`) {
329
+ selector = modifier + selector;
330
+ }
331
+ else if (preserveModifier !== false) {
332
+ const referenceRange = typeof preserveModifier === `string`
333
+ ? preserveModifier
334
+ : latestDescriptor.range;
335
+ const modifier = extractRangeModifier(referenceRange, { project });
336
+ selector = modifier + selector;
337
+ }
338
+ const screeningDescriptor = core_2.structUtils.makeDescriptor(bestLocator, core_2.structUtils.makeRange({ protocol, source, params, selector }));
339
+ const screeningLocators = await resolver.getCandidates(project.configuration.normalizeDependency(screeningDescriptor), {}, resolveOptions);
340
+ // If turning 1.0.0 into ^1.0.0 would cause it to resolve to something else
341
+ // (for example 1.1.0), then we don't add the modifier.
342
+ //
343
+ // This is to account for "weird" release strategies where things like
344
+ // prereleases are released as older versions than the latest available
345
+ // ones.
346
+ //
347
+ // Ex 1: https://github.com/parcel-bundler/parcel/issues/8010
348
+ // Ex 2: https://github.com/sveltejs/kit/discussions/4645
349
+ if (screeningLocators.length !== 1) {
350
+ selector = rawSelector;
351
+ }
335
352
  }
336
353
  return core_2.structUtils.makeDescriptor(bestLocator, core_2.structUtils.makeRange({ protocol, source, params, selector }));
337
354
  }
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@yarnpkg/plugin-essentials",
3
- "version": "3.2.0",
3
+ "version": "4.0.0-rc.3",
4
4
  "license": "BSD-2-Clause",
5
5
  "main": "./lib/index.js",
6
6
  "dependencies": {
7
- "@yarnpkg/fslib": "^2.6.1",
8
- "@yarnpkg/json-proxy": "^2.1.1",
9
- "@yarnpkg/parsers": "^2.5.0",
7
+ "@yarnpkg/fslib": "^3.0.0-rc.3",
8
+ "@yarnpkg/parsers": "^3.0.0-rc.3",
10
9
  "ci-info": "^3.2.0",
11
- "clipanion": "^3.2.0-rc.4",
10
+ "clipanion": "^3.2.0-rc.10",
12
11
  "enquirer": "^2.3.6",
13
12
  "lodash": "^4.17.15",
14
13
  "micromatch": "^4.0.2",
@@ -17,18 +16,17 @@
17
16
  "typanion": "^3.3.0"
18
17
  },
19
18
  "peerDependencies": {
20
- "@yarnpkg/cli": "^3.2.0",
21
- "@yarnpkg/core": "^3.2.0",
22
- "@yarnpkg/plugin-git": "^2.6.0"
19
+ "@yarnpkg/cli": "^4.0.0-rc.3",
20
+ "@yarnpkg/core": "^4.0.0-rc.3",
21
+ "@yarnpkg/plugin-git": "^3.0.0-rc.3"
23
22
  },
24
23
  "devDependencies": {
25
24
  "@types/lodash": "^4.14.136",
26
25
  "@types/micromatch": "^4.0.1",
27
26
  "@types/semver": "^7.1.0",
28
- "@types/treeify": "^1.0.0",
29
- "@yarnpkg/cli": "^3.2.0",
30
- "@yarnpkg/core": "^3.2.0",
31
- "@yarnpkg/plugin-git": "^2.6.0"
27
+ "@yarnpkg/cli": "^4.0.0-rc.3",
28
+ "@yarnpkg/core": "^4.0.0-rc.3",
29
+ "@yarnpkg/plugin-git": "^3.0.0-rc.3"
32
30
  },
33
31
  "repository": {
34
32
  "type": "git",
@@ -47,7 +45,8 @@
47
45
  "/lib/**/*"
48
46
  ],
49
47
  "engines": {
50
- "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0"
48
+ "node": ">=14.15.0"
51
49
  },
50
+ "stableVersion": "3.2.0",
52
51
  "typings": "./lib/index.d.ts"
53
52
  }