UNPKG

49.8 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Configuration = exports.ProjectLookup = exports.coreDefinitions = exports.formatColors = exports.FormatType = exports.SettingsType = exports.SECRET = exports.DEFAULT_LOCK_FILENAME = exports.DEFAULT_RC_FILENAME = exports.ENVIRONMENT_PREFIX = void 0;
4const tslib_1 = require("tslib");
5const fslib_1 = require("@yarnpkg/fslib");
6const fslib_2 = require("@yarnpkg/fslib");
7const parsers_1 = require("@yarnpkg/parsers");
8const camelcase_1 = tslib_1.__importDefault(require("camelcase"));
9const chalk_1 = tslib_1.__importDefault(require("chalk"));
10const ci_info_1 = require("ci-info");
11const clipanion_1 = require("clipanion");
12const p_limit_1 = tslib_1.__importDefault(require("p-limit"));
13const semver_1 = tslib_1.__importDefault(require("semver"));
14const stream_1 = require("stream");
15const CorePlugin_1 = require("./CorePlugin");
16const Manifest_1 = require("./Manifest");
17const MultiFetcher_1 = require("./MultiFetcher");
18const MultiResolver_1 = require("./MultiResolver");
19const ProtocolResolver_1 = require("./ProtocolResolver");
20const VirtualFetcher_1 = require("./VirtualFetcher");
21const VirtualResolver_1 = require("./VirtualResolver");
22const WorkspaceFetcher_1 = require("./WorkspaceFetcher");
23const WorkspaceResolver_1 = require("./WorkspaceResolver");
24const folderUtils = tslib_1.__importStar(require("./folderUtils"));
25const miscUtils = tslib_1.__importStar(require("./miscUtils"));
26const nodeUtils = tslib_1.__importStar(require("./nodeUtils"));
27const semverUtils = tslib_1.__importStar(require("./semverUtils"));
28const structUtils = tslib_1.__importStar(require("./structUtils"));
29const chalkOptions = process.env.GITHUB_ACTIONS
30 ? { level: 2 }
31 : chalk_1.default.supportsColor
32 ? { level: chalk_1.default.supportsColor.level }
33 : { level: 0 };
34const supportsColor = chalkOptions.level !== 0;
35const supportsHyperlinks = supportsColor && !process.env.GITHUB_ACTIONS;
36const chalkInstance = new chalk_1.default.Instance(chalkOptions);
37const IGNORED_ENV_VARIABLES = new Set([
38 // "binFolder" is the magic location where the parent process stored the
39 // current binaries; not an actual configuration settings
40 `binFolder`,
41 // "version" is set by Docker:
42 // https://github.com/nodejs/docker-node/blob/5a6a5e91999358c5b04fddd6c22a9a4eb0bf3fbf/10/alpine/Dockerfile#L51
43 `version`,
44 // "flags" is set by Netlify; they use it to specify the flags to send to the
45 // CLI when running the automatic `yarn install`
46 `flags`,
47 // "gpg" and "profile" are used by the install.sh script:
48 // https://classic.yarnpkg.com/install.sh
49 `profile`,
50 `gpg`,
51 // "ignoreNode" is used to disable the Node version check
52 `ignoreNode`,
53 // "wrapOutput" was a variable used to indicate nested "yarn run" processes
54 // back in Yarn 1.
55 `wrapOutput`,
56]);
57exports.ENVIRONMENT_PREFIX = `yarn_`;
58exports.DEFAULT_RC_FILENAME = `.yarnrc.yml`;
59exports.DEFAULT_LOCK_FILENAME = `yarn.lock`;
60exports.SECRET = `********`;
61var SettingsType;
62(function (SettingsType) {
63 SettingsType["ANY"] = "ANY";
64 SettingsType["BOOLEAN"] = "BOOLEAN";
65 SettingsType["ABSOLUTE_PATH"] = "ABSOLUTE_PATH";
66 SettingsType["LOCATOR"] = "LOCATOR";
67 SettingsType["LOCATOR_LOOSE"] = "LOCATOR_LOOSE";
68 SettingsType["NUMBER"] = "NUMBER";
69 SettingsType["STRING"] = "STRING";
70 SettingsType["SECRET"] = "SECRET";
71 SettingsType["SHAPE"] = "SHAPE";
72 SettingsType["MAP"] = "MAP";
73})(SettingsType = exports.SettingsType || (exports.SettingsType = {}));
74var FormatType;
75(function (FormatType) {
76 FormatType["NAME"] = "NAME";
77 FormatType["NUMBER"] = "NUMBER";
78 FormatType["PATH"] = "PATH";
79 FormatType["RANGE"] = "RANGE";
80 FormatType["REFERENCE"] = "REFERENCE";
81 FormatType["SCOPE"] = "SCOPE";
82 FormatType["ADDED"] = "ADDED";
83 FormatType["REMOVED"] = "REMOVED";
84 FormatType["CODE"] = "CODE";
85})(FormatType = exports.FormatType || (exports.FormatType = {}));
86exports.formatColors = chalkOptions.level >= 3 ? new Map([
87 [FormatType.NAME, `#d7875f`],
88 [FormatType.RANGE, `#00afaf`],
89 [FormatType.REFERENCE, `#87afff`],
90 [FormatType.NUMBER, `#ffd700`],
91 [FormatType.PATH, `#d75fd7`],
92 [FormatType.SCOPE, `#d75f00`],
93 [FormatType.ADDED, `#5faf00`],
94 [FormatType.REMOVED, `#d70000`],
95 [FormatType.CODE, `#87afff`],
96]) : new Map([
97 [FormatType.NAME, 173],
98 [FormatType.RANGE, 37],
99 [FormatType.REFERENCE, 111],
100 [FormatType.NUMBER, 220],
101 [FormatType.PATH, 170],
102 [FormatType.SCOPE, 166],
103 [FormatType.ADDED, 70],
104 [FormatType.REMOVED, 160],
105 [FormatType.CODE, 111],
106]);
107// General rules:
108//
109// - filenames that don't accept actual paths must end with the "Filename" suffix
110// prefer to use absolute paths instead, since they are automatically resolved
111// ex: lockfileFilename
112//
113// - folders must end with the "Folder" suffix
114// ex: cacheFolder, pnpVirtualFolder
115//
116// - actual paths to a file must end with the "Path" suffix
117// ex: pnpPath
118//
119// - options that tweaks the strictness must begin with the "allow" prefix
120// ex: allowInvalidChecksums
121//
122// - options that enable a feature must begin with the "enable" prefix
123// ex: enableEmojis, enableColors
124exports.coreDefinitions = {
125 // Not implemented for now, but since it's part of all Yarn installs we want to declare it in order to improve drop-in compatibility
126 lastUpdateCheck: {
127 description: `Last timestamp we checked whether new Yarn versions were available`,
128 type: SettingsType.STRING,
129 default: null,
130 },
131 // Settings related to proxying all Yarn calls to a specific executable
132 yarnPath: {
133 description: `Path to the local executable that must be used over the global one`,
134 type: SettingsType.ABSOLUTE_PATH,
135 default: null,
136 },
137 ignorePath: {
138 description: `If true, the local executable will be ignored when using the global one`,
139 type: SettingsType.BOOLEAN,
140 default: false,
141 },
142 ignoreCwd: {
143 description: `If true, the \`--cwd\` flag will be ignored`,
144 type: SettingsType.BOOLEAN,
145 default: false,
146 },
147 // Settings related to the package manager internal names
148 cacheKeyOverride: {
149 description: `A global cache key override; used only for test purposes`,
150 type: SettingsType.STRING,
151 default: null,
152 },
153 globalFolder: {
154 description: `Folder where are stored the system-wide settings`,
155 type: SettingsType.ABSOLUTE_PATH,
156 default: folderUtils.getDefaultGlobalFolder(),
157 },
158 cacheFolder: {
159 description: `Folder where the cache files must be written`,
160 type: SettingsType.ABSOLUTE_PATH,
161 default: `./.yarn/cache`,
162 },
163 compressionLevel: {
164 description: `Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)`,
165 type: SettingsType.NUMBER,
166 values: [`mixed`, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
167 default: fslib_1.DEFAULT_COMPRESSION_LEVEL,
168 },
169 virtualFolder: {
170 description: `Folder where the virtual packages (cf doc) will be mapped on the disk (must be named $$virtual)`,
171 type: SettingsType.ABSOLUTE_PATH,
172 default: `./.yarn/$$virtual`,
173 },
174 bstatePath: {
175 description: `Path of the file where the current state of the built packages must be stored`,
176 type: SettingsType.ABSOLUTE_PATH,
177 default: `./.yarn/build-state.yml`,
178 },
179 lockfileFilename: {
180 description: `Name of the files where the Yarn dependency tree entries must be stored`,
181 type: SettingsType.STRING,
182 default: exports.DEFAULT_LOCK_FILENAME,
183 },
184 installStatePath: {
185 description: `Path of the file where the install state will be persisted`,
186 type: SettingsType.ABSOLUTE_PATH,
187 default: `./.yarn/install-state.gz`,
188 },
189 immutablePatterns: {
190 description: `Array of glob patterns; files matching them won't be allowed to change during immutable installs`,
191 type: SettingsType.STRING,
192 default: [],
193 isArray: true,
194 },
195 rcFilename: {
196 description: `Name of the files where the configuration can be found`,
197 type: SettingsType.STRING,
198 default: getRcFilename(),
199 },
200 enableGlobalCache: {
201 description: `If true, the system-wide cache folder will be used regardless of \`cache-folder\``,
202 type: SettingsType.BOOLEAN,
203 default: false,
204 },
205 enableAbsoluteVirtuals: {
206 description: `If true, the virtual symlinks will use absolute paths if required [non portable!!]`,
207 type: SettingsType.BOOLEAN,
208 default: false,
209 },
210 // Settings related to the output style
211 enableColors: {
212 description: `If true, the CLI is allowed to use colors in its output`,
213 type: SettingsType.BOOLEAN,
214 default: supportsColor,
215 defaultText: `<dynamic>`,
216 },
217 enableHyperlinks: {
218 description: `If true, the CLI is allowed to use hyperlinks in its output`,
219 type: SettingsType.BOOLEAN,
220 default: supportsHyperlinks,
221 defaultText: `<dynamic>`,
222 },
223 enableInlineBuilds: {
224 description: `If true, the CLI will print the build output on the command line`,
225 type: SettingsType.BOOLEAN,
226 default: ci_info_1.isCI,
227 defaultText: `<dynamic>`,
228 },
229 enableProgressBars: {
230 description: `If true, the CLI is allowed to show a progress bar for long-running events`,
231 type: SettingsType.BOOLEAN,
232 default: !ci_info_1.isCI && process.stdout.isTTY && process.stdout.columns > 22,
233 defaultText: `<dynamic>`,
234 },
235 enableTimers: {
236 description: `If true, the CLI is allowed to print the time spent executing commands`,
237 type: SettingsType.BOOLEAN,
238 default: true,
239 },
240 preferAggregateCacheInfo: {
241 description: `If true, the CLI will only print a one-line report of any cache changes`,
242 type: SettingsType.BOOLEAN,
243 default: ci_info_1.isCI,
244 },
245 preferInteractive: {
246 description: `If true, the CLI will automatically use the interactive mode when called from a TTY`,
247 type: SettingsType.BOOLEAN,
248 default: false,
249 },
250 preferTruncatedLines: {
251 description: `If true, the CLI will truncate lines that would go beyond the size of the terminal`,
252 type: SettingsType.BOOLEAN,
253 default: false,
254 },
255 progressBarStyle: {
256 description: `Which style of progress bar should be used (only when progress bars are enabled)`,
257 type: SettingsType.STRING,
258 default: undefined,
259 defaultText: `<dynamic>`,
260 },
261 // Settings related to how packages are interpreted by default
262 defaultLanguageName: {
263 description: `Default language mode that should be used when a package doesn't offer any insight`,
264 type: SettingsType.STRING,
265 default: `node`,
266 },
267 defaultProtocol: {
268 description: `Default resolution protocol used when resolving pure semver and tag ranges`,
269 type: SettingsType.STRING,
270 default: `npm:`,
271 },
272 enableTransparentWorkspaces: {
273 description: `If false, Yarn won't automatically resolve workspace dependencies unless they use the \`workspace:\` protocol`,
274 type: SettingsType.BOOLEAN,
275 default: true,
276 },
277 // Settings related to network access
278 enableMirror: {
279 description: `If true, the downloaded packages will be retrieved and stored in both the local and global folders`,
280 type: SettingsType.BOOLEAN,
281 default: true,
282 },
283 enableNetwork: {
284 description: `If false, the package manager will refuse to use the network if required to`,
285 type: SettingsType.BOOLEAN,
286 default: true,
287 },
288 httpProxy: {
289 description: `URL of the http proxy that must be used for outgoing http requests`,
290 type: SettingsType.STRING,
291 default: null,
292 },
293 httpsProxy: {
294 description: `URL of the http proxy that must be used for outgoing https requests`,
295 type: SettingsType.STRING,
296 default: null,
297 },
298 unsafeHttpWhitelist: {
299 description: `List of the hostnames for which http queries are allowed (glob patterns are supported)`,
300 type: SettingsType.STRING,
301 default: [],
302 isArray: true,
303 },
304 httpTimeout: {
305 description: `Timeout of each http request in milliseconds`,
306 type: SettingsType.NUMBER,
307 default: 60000,
308 },
309 httpRetry: {
310 description: `Retry times on http failure`,
311 type: SettingsType.NUMBER,
312 default: 3,
313 },
314 networkConcurrency: {
315 description: `Maximal number of concurrent requests`,
316 type: SettingsType.NUMBER,
317 default: Infinity,
318 },
319 // Settings related to telemetry
320 enableTelemetry: {
321 description: `If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry`,
322 type: SettingsType.BOOLEAN,
323 default: !ci_info_1.isCI,
324 },
325 telemetryInterval: {
326 description: `Minimal amount of time between two telemetry uploads, in days`,
327 type: SettingsType.NUMBER,
328 default: 7,
329 },
330 telemetryUserId: {
331 description: `If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.`,
332 type: SettingsType.STRING,
333 default: null,
334 },
335 // Settings related to security
336 enableScripts: {
337 description: `If true, packages are allowed to have install scripts by default`,
338 type: SettingsType.BOOLEAN,
339 default: true,
340 },
341 enableImmutableCache: {
342 description: `If true, the cache is reputed immutable and actions that would modify it will throw`,
343 type: SettingsType.BOOLEAN,
344 default: false,
345 },
346 checksumBehavior: {
347 description: `Enumeration defining what to do when a checksum doesn't match expectations`,
348 type: SettingsType.STRING,
349 default: `throw`,
350 },
351 // Package patching - to fix incorrect definitions
352 packageExtensions: {
353 description: `Map of package corrections to apply on the dependency tree`,
354 type: SettingsType.MAP,
355 valueDefinition: {
356 description: ``,
357 type: SettingsType.ANY,
358 },
359 },
360};
361function parseBoolean(value) {
362 switch (value) {
363 case `true`:
364 case `1`:
365 case 1:
366 case true:
367 {
368 return true;
369 }
370 break;
371 case `false`:
372 case `0`:
373 case 0:
374 case false:
375 {
376 return false;
377 }
378 break;
379 default:
380 {
381 throw new Error(`Couldn't parse "${value}" as a boolean`);
382 }
383 break;
384 }
385}
386function parseValue(configuration, path, value, definition, folder) {
387 if (definition.isArray) {
388 if (!Array.isArray(value)) {
389 return String(value).split(/,/).map(segment => {
390 return parseSingleValue(configuration, path, segment, definition, folder);
391 });
392 }
393 else {
394 return value.map((sub, i) => parseSingleValue(configuration, `${path}[${i}]`, sub, definition, folder));
395 }
396 }
397 else {
398 if (Array.isArray(value)) {
399 throw new Error(`Non-array configuration settings "${path}" cannot be an array`);
400 }
401 else {
402 return parseSingleValue(configuration, path, value, definition, folder);
403 }
404 }
405}
406function parseSingleValue(configuration, path, value, definition, folder) {
407 var _a;
408 switch (definition.type) {
409 case SettingsType.ANY:
410 return value;
411 case SettingsType.SHAPE:
412 return parseShape(configuration, path, value, definition, folder);
413 case SettingsType.MAP:
414 return parseMap(configuration, path, value, definition, folder);
415 }
416 if (value === null && !definition.isNullable && definition.default !== null)
417 throw new Error(`Non-nullable configuration settings "${path}" cannot be set to null`);
418 if ((_a = definition.values) === null || _a === void 0 ? void 0 : _a.includes(value))
419 return value;
420 const interpretValue = () => {
421 if (definition.type === SettingsType.BOOLEAN)
422 return parseBoolean(value);
423 if (typeof value !== `string`)
424 throw new Error(`Expected value (${value}) to be a string`);
425 const valueWithReplacedVariables = miscUtils.replaceEnvVariables(value, {
426 env: process.env,
427 });
428 switch (definition.type) {
429 case SettingsType.ABSOLUTE_PATH:
430 return fslib_2.ppath.resolve(folder, fslib_2.npath.toPortablePath(valueWithReplacedVariables));
431 case SettingsType.LOCATOR_LOOSE:
432 return structUtils.parseLocator(valueWithReplacedVariables, false);
433 case SettingsType.NUMBER:
434 return parseInt(valueWithReplacedVariables);
435 case SettingsType.LOCATOR:
436 return structUtils.parseLocator(valueWithReplacedVariables);
437 default:
438 return valueWithReplacedVariables;
439 }
440 };
441 const interpreted = interpretValue();
442 if (definition.values && !definition.values.includes(interpreted))
443 throw new Error(`Invalid value, expected one of ${definition.values.join(`, `)}`);
444 return interpreted;
445}
446function parseShape(configuration, path, value, definition, folder) {
447 if (typeof value !== `object` || Array.isArray(value))
448 throw new clipanion_1.UsageError(`Object configuration settings "${path}" must be an object`);
449 const result = getDefaultValue(configuration, definition);
450 if (value === null)
451 return result;
452 for (const [propKey, propValue] of Object.entries(value)) {
453 const subPath = `${path}.${propKey}`;
454 const subDefinition = definition.properties[propKey];
455 if (!subDefinition)
456 throw new clipanion_1.UsageError(`Unrecognized configuration settings found: ${path}.${propKey} - run "yarn config -v" to see the list of settings supported in Yarn`);
457 result.set(propKey, parseValue(configuration, subPath, propValue, definition.properties[propKey], folder));
458 }
459 return result;
460}
461function parseMap(configuration, path, value, definition, folder) {
462 const result = new Map();
463 if (typeof value !== `object` || Array.isArray(value))
464 throw new clipanion_1.UsageError(`Map configuration settings "${path}" must be an object`);
465 if (value === null)
466 return result;
467 for (const [propKey, propValue] of Object.entries(value)) {
468 const normalizedKey = definition.normalizeKeys ? definition.normalizeKeys(propKey) : propKey;
469 const subPath = `${path}['${normalizedKey}']`;
470 // @ts-expect-error: SettingsDefinitionNoDefault has ... no default ... but
471 // that's fine because we're guaranteed it's not undefined.
472 const valueDefinition = definition.valueDefinition;
473 result.set(normalizedKey, parseValue(configuration, subPath, propValue, valueDefinition, folder));
474 }
475 return result;
476}
477function getDefaultValue(configuration, definition) {
478 switch (definition.type) {
479 case SettingsType.SHAPE:
480 {
481 const result = new Map();
482 for (const [propKey, propDefinition] of Object.entries(definition.properties))
483 result.set(propKey, getDefaultValue(configuration, propDefinition));
484 return result;
485 }
486 break;
487 case SettingsType.MAP:
488 {
489 return new Map();
490 }
491 break;
492 case SettingsType.ABSOLUTE_PATH:
493 {
494 if (definition.default === null)
495 return null;
496 if (configuration.projectCwd === null) {
497 if (fslib_2.ppath.isAbsolute(definition.default)) {
498 return fslib_2.ppath.normalize(definition.default);
499 }
500 else if (definition.isNullable) {
501 return null;
502 }
503 else {
504 // Reached when a relative path is the default but the current
505 // context is evaluated outside of a Yarn project
506 return undefined;
507 }
508 }
509 else {
510 if (Array.isArray(definition.default)) {
511 return definition.default.map((entry) => fslib_2.ppath.resolve(configuration.projectCwd, entry));
512 }
513 else {
514 return fslib_2.ppath.resolve(configuration.projectCwd, definition.default);
515 }
516 }
517 }
518 break;
519 default:
520 {
521 return definition.default;
522 }
523 break;
524 }
525}
526function transformConfiguration(rawValue, definition, transforms) {
527 if (definition.type === SettingsType.SECRET && typeof rawValue === `string` && transforms.hideSecrets)
528 return exports.SECRET;
529 if (definition.type === SettingsType.ABSOLUTE_PATH && typeof rawValue === `string` && transforms.getNativePaths)
530 return fslib_2.npath.fromPortablePath(rawValue);
531 if (definition.isArray && Array.isArray(rawValue)) {
532 const newValue = [];
533 for (const value of rawValue)
534 newValue.push(transformConfiguration(value, definition, transforms));
535 return newValue;
536 }
537 if (definition.type === SettingsType.MAP && rawValue instanceof Map) {
538 const newValue = new Map();
539 for (const [key, value] of rawValue.entries())
540 newValue.set(key, transformConfiguration(value, definition.valueDefinition, transforms));
541 return newValue;
542 }
543 if (definition.type === SettingsType.SHAPE && rawValue instanceof Map) {
544 const newValue = new Map();
545 for (const [key, value] of rawValue.entries()) {
546 const propertyDefinition = definition.properties[key];
547 newValue.set(key, transformConfiguration(value, propertyDefinition, transforms));
548 }
549 return newValue;
550 }
551 return rawValue;
552}
553function getEnvironmentSettings() {
554 const environmentSettings = {};
555 for (let [key, value] of Object.entries(process.env)) {
556 key = key.toLowerCase();
557 if (!key.startsWith(exports.ENVIRONMENT_PREFIX))
558 continue;
559 key = camelcase_1.default(key.slice(exports.ENVIRONMENT_PREFIX.length));
560 environmentSettings[key] = value;
561 }
562 return environmentSettings;
563}
564function getRcFilename() {
565 const rcKey = `${exports.ENVIRONMENT_PREFIX}rc_filename`;
566 for (const [key, value] of Object.entries(process.env))
567 if (key.toLowerCase() === rcKey && typeof value === `string`)
568 return value;
569 return exports.DEFAULT_RC_FILENAME;
570}
571var ProjectLookup;
572(function (ProjectLookup) {
573 ProjectLookup[ProjectLookup["LOCKFILE"] = 0] = "LOCKFILE";
574 ProjectLookup[ProjectLookup["MANIFEST"] = 1] = "MANIFEST";
575 ProjectLookup[ProjectLookup["NONE"] = 2] = "NONE";
576})(ProjectLookup = exports.ProjectLookup || (exports.ProjectLookup = {}));
577class Configuration {
578 constructor(startingCwd) {
579 this.projectCwd = null;
580 this.plugins = new Map();
581 this.settings = new Map();
582 this.values = new Map();
583 this.sources = new Map();
584 this.invalid = new Map();
585 this.packageExtensions = new Map();
586 this.limits = new Map();
587 this.startingCwd = startingCwd;
588 }
589 static create(startingCwd, projectCwdOrPlugins, maybePlugins) {
590 const configuration = new Configuration(startingCwd);
591 if (typeof projectCwdOrPlugins !== `undefined` && !(projectCwdOrPlugins instanceof Map))
592 configuration.projectCwd = projectCwdOrPlugins;
593 configuration.importSettings(exports.coreDefinitions);
594 const plugins = typeof maybePlugins !== `undefined`
595 ? maybePlugins
596 : projectCwdOrPlugins instanceof Map
597 ? projectCwdOrPlugins
598 : new Map();
599 for (const [name, plugin] of plugins)
600 configuration.activatePlugin(name, plugin);
601 return configuration;
602 }
603 /**
604 * Instantiate a new configuration object exposing the configuration obtained
605 * from reading the various rc files and the environment settings.
606 *
607 * The `pluginConfiguration` parameter is expected to indicate:
608 *
609 * 1. which modules should be made available to plugins when they require a
610 * package (this is the dynamic linking part - for example we want all the
611 * plugins to use the exact same version of @yarnpkg/core, which also is the
612 * version used by the running Yarn instance).
613 *
614 * 2. which of those modules are actually plugins that need to be injected
615 * within the configuration.
616 *
617 * Note that some extra plugins will be automatically added based on the
618 * content of the rc files - with the rc plugins taking precedence over
619 * the other ones.
620 *
621 * One particularity: the plugin initialization order is quite strict, with
622 * plugins listed in /foo/bar/.yarnrc.yml taking precedence over plugins
623 * listed in /foo/.yarnrc.yml and /.yarnrc.yml. Additionally, while plugins
624 * can depend on one another, they can only depend on plugins that have been
625 * instantiated before them (so a plugin listed in /foo/.yarnrc.yml can
626 * depend on another one listed on /foo/bar/.yarnrc.yml, but not the other
627 * way around).
628 */
629 static async find(startingCwd, pluginConfiguration, { lookup = ProjectLookup.LOCKFILE, strict = true, usePath = false, useRc = true } = {}) {
630 const environmentSettings = getEnvironmentSettings();
631 delete environmentSettings.rcFilename;
632 const rcFiles = await Configuration.findRcFiles(startingCwd);
633 const homeRcFile = await Configuration.findHomeRcFile();
634 const pickCoreFields = ({ ignoreCwd, yarnPath, ignorePath, lockfileFilename }) => ({ ignoreCwd, yarnPath, ignorePath, lockfileFilename });
635 const excludeCoreFields = ({ ignoreCwd, yarnPath, ignorePath, lockfileFilename, ...rest }) => rest;
636 const configuration = new Configuration(startingCwd);
637 configuration.importSettings(pickCoreFields(exports.coreDefinitions));
638 configuration.useWithSource(`<environment>`, pickCoreFields(environmentSettings), startingCwd, { strict: false });
639 for (const { path, cwd, data } of rcFiles)
640 configuration.useWithSource(path, pickCoreFields(data), cwd, { strict: false });
641 if (homeRcFile)
642 configuration.useWithSource(homeRcFile.path, pickCoreFields(homeRcFile.data), homeRcFile.cwd, { strict: false });
643 if (usePath) {
644 const yarnPath = configuration.get(`yarnPath`);
645 const ignorePath = configuration.get(`ignorePath`);
646 if (yarnPath !== null && !ignorePath) {
647 return configuration;
648 }
649 }
650 // We need to know the project root before being able to truly instantiate
651 // our configuration, and to know that we need to know the lockfile name
652 const lockfileFilename = configuration.get(`lockfileFilename`);
653 let projectCwd;
654 switch (lookup) {
655 case ProjectLookup.LOCKFILE:
656 {
657 projectCwd = await Configuration.findProjectCwd(startingCwd, lockfileFilename);
658 }
659 break;
660 case ProjectLookup.MANIFEST:
661 {
662 projectCwd = await Configuration.findProjectCwd(startingCwd, null);
663 }
664 break;
665 case ProjectLookup.NONE:
666 {
667 if (fslib_2.xfs.existsSync(fslib_2.ppath.join(startingCwd, `package.json`))) {
668 projectCwd = fslib_2.ppath.resolve(startingCwd);
669 }
670 else {
671 projectCwd = null;
672 }
673 }
674 break;
675 }
676 // Great! We now have enough information to really start to setup the
677 // core configuration object.
678 configuration.startingCwd = startingCwd;
679 configuration.projectCwd = projectCwd;
680 configuration.importSettings(excludeCoreFields(exports.coreDefinitions));
681 // Now that the configuration object is almost ready, we need to load all
682 // the configured plugins
683 const plugins = new Map([
684 [`@@core`, CorePlugin_1.CorePlugin],
685 ]);
686 const interop = (obj) => obj.__esModule
687 ? obj.default
688 : obj;
689 if (pluginConfiguration !== null) {
690 for (const request of pluginConfiguration.plugins.keys())
691 plugins.set(request, interop(pluginConfiguration.modules.get(request)));
692 const requireEntries = new Map();
693 for (const request of nodeUtils.builtinModules())
694 requireEntries.set(request, () => nodeUtils.dynamicRequire(request));
695 for (const [request, embedModule] of pluginConfiguration.modules)
696 requireEntries.set(request, () => embedModule);
697 const dynamicPlugins = new Set();
698 const getDefault = (object) => {
699 return object.default || object;
700 };
701 const importPlugin = (pluginPath, source) => {
702 const { factory, name } = nodeUtils.dynamicRequire(fslib_2.npath.fromPortablePath(pluginPath));
703 // Prevent plugin redefinition so that the ones declared deeper in the
704 // filesystem always have precedence over the ones below.
705 if (dynamicPlugins.has(name))
706 return;
707 const pluginRequireEntries = new Map(requireEntries);
708 const pluginRequire = (request) => {
709 if (pluginRequireEntries.has(request)) {
710 return pluginRequireEntries.get(request)();
711 }
712 else {
713 throw new clipanion_1.UsageError(`This plugin cannot access the package referenced via ${request} which is neither a builtin, nor an exposed entry`);
714 }
715 };
716 const plugin = miscUtils.prettifySyncErrors(() => {
717 return getDefault(factory(pluginRequire));
718 }, message => {
719 return `${message} (when initializing ${name}, defined in ${source})`;
720 });
721 requireEntries.set(name, () => plugin);
722 dynamicPlugins.add(name);
723 plugins.set(name, plugin);
724 };
725 if (environmentSettings.plugins) {
726 for (const userProvidedPath of environmentSettings.plugins.split(`;`)) {
727 const pluginPath = fslib_2.ppath.resolve(startingCwd, fslib_2.npath.toPortablePath(userProvidedPath));
728 importPlugin(pluginPath, `<environment>`);
729 }
730 }
731 for (const { path, cwd, data } of rcFiles) {
732 if (!useRc)
733 continue;
734 if (!Array.isArray(data.plugins))
735 continue;
736 for (const userPluginEntry of data.plugins) {
737 const userProvidedPath = typeof userPluginEntry !== `string`
738 ? userPluginEntry.path
739 : userPluginEntry;
740 const pluginPath = fslib_2.ppath.resolve(cwd, fslib_2.npath.toPortablePath(userProvidedPath));
741 importPlugin(pluginPath, path);
742 }
743 }
744 }
745 for (const [name, plugin] of plugins)
746 configuration.activatePlugin(name, plugin);
747 configuration.useWithSource(`<environment>`, excludeCoreFields(environmentSettings), startingCwd, { strict });
748 for (const { path, cwd, data } of rcFiles)
749 configuration.useWithSource(path, excludeCoreFields(data), cwd, { strict });
750 // The home configuration is never strict because it improves support for
751 // multiple projects using different Yarn versions on the same machine
752 if (homeRcFile)
753 configuration.useWithSource(homeRcFile.path, excludeCoreFields(homeRcFile.data), homeRcFile.cwd, { strict: false });
754 if (configuration.get(`enableGlobalCache`)) {
755 configuration.values.set(`cacheFolder`, `${configuration.get(`globalFolder`)}/cache`);
756 configuration.sources.set(`cacheFolder`, `<internal>`);
757 }
758 await configuration.refreshPackageExtensions();
759 return configuration;
760 }
761 static async findRcFiles(startingCwd) {
762 const rcFilename = getRcFilename();
763 const rcFiles = [];
764 let nextCwd = startingCwd;
765 let currentCwd = null;
766 while (nextCwd !== currentCwd) {
767 currentCwd = nextCwd;
768 const rcPath = fslib_2.ppath.join(currentCwd, rcFilename);
769 if (fslib_2.xfs.existsSync(rcPath)) {
770 const content = await fslib_2.xfs.readFilePromise(rcPath, `utf8`);
771 let data;
772 try {
773 data = parsers_1.parseSyml(content);
774 }
775 catch (error) {
776 let tip = ``;
777 if (content.match(/^\s+(?!-)[^:]+\s+\S+/m))
778 tip = ` (in particular, make sure you list the colons after each key name)`;
779 throw new clipanion_1.UsageError(`Parse error when loading ${rcPath}; please check it's proper Yaml${tip}`);
780 }
781 rcFiles.push({ path: rcPath, cwd: currentCwd, data });
782 }
783 nextCwd = fslib_2.ppath.dirname(currentCwd);
784 }
785 return rcFiles;
786 }
787 static async findHomeRcFile() {
788 const rcFilename = getRcFilename();
789 const homeFolder = folderUtils.getHomeFolder();
790 const homeRcFilePath = fslib_2.ppath.join(homeFolder, rcFilename);
791 if (fslib_2.xfs.existsSync(homeRcFilePath)) {
792 const content = await fslib_2.xfs.readFilePromise(homeRcFilePath, `utf8`);
793 const data = parsers_1.parseSyml(content);
794 return { path: homeRcFilePath, cwd: homeFolder, data };
795 }
796 return null;
797 }
798 static async findProjectCwd(startingCwd, lockfileFilename) {
799 let projectCwd = null;
800 let nextCwd = startingCwd;
801 let currentCwd = null;
802 while (nextCwd !== currentCwd) {
803 currentCwd = nextCwd;
804 if (fslib_2.xfs.existsSync(fslib_2.ppath.join(currentCwd, `package.json`)))
805 projectCwd = currentCwd;
806 if (lockfileFilename !== null) {
807 if (fslib_2.xfs.existsSync(fslib_2.ppath.join(currentCwd, lockfileFilename))) {
808 projectCwd = currentCwd;
809 break;
810 }
811 }
812 else {
813 if (projectCwd !== null) {
814 break;
815 }
816 }
817 nextCwd = fslib_2.ppath.dirname(currentCwd);
818 }
819 return projectCwd;
820 }
821 static async updateConfiguration(cwd, patch) {
822 const rcFilename = getRcFilename();
823 const configurationPath = fslib_2.ppath.join(cwd, rcFilename);
824 const current = fslib_2.xfs.existsSync(configurationPath)
825 ? parsers_1.parseSyml(await fslib_2.xfs.readFilePromise(configurationPath, `utf8`))
826 : {};
827 let patched = false;
828 let replacement;
829 if (typeof patch === `function`) {
830 try {
831 replacement = patch(current);
832 }
833 catch (_a) {
834 replacement = patch({});
835 }
836 if (replacement === current) {
837 return;
838 }
839 }
840 else {
841 replacement = current;
842 for (const key of Object.keys(patch)) {
843 const currentValue = current[key];
844 const patchField = patch[key];
845 let nextValue;
846 if (typeof patchField === `function`) {
847 try {
848 nextValue = patchField(currentValue);
849 }
850 catch (_b) {
851 nextValue = patchField(undefined);
852 }
853 }
854 else {
855 nextValue = patchField;
856 }
857 if (currentValue === nextValue)
858 continue;
859 replacement[key] = nextValue;
860 patched = true;
861 }
862 if (!patched) {
863 return;
864 }
865 }
866 await fslib_2.xfs.changeFilePromise(configurationPath, parsers_1.stringifySyml(replacement), {
867 automaticNewlines: true,
868 });
869 }
870 static async updateHomeConfiguration(patch) {
871 const homeFolder = folderUtils.getHomeFolder();
872 return await Configuration.updateConfiguration(homeFolder, patch);
873 }
874 activatePlugin(name, plugin) {
875 this.plugins.set(name, plugin);
876 if (typeof plugin.configuration !== `undefined`) {
877 this.importSettings(plugin.configuration);
878 }
879 }
880 importSettings(definitions) {
881 for (const [name, definition] of Object.entries(definitions)) {
882 if (this.settings.has(name))
883 throw new Error(`Cannot redefine settings "${name}"`);
884 this.settings.set(name, definition);
885 this.values.set(name, getDefaultValue(this, definition));
886 }
887 }
888 useWithSource(source, data, folder, { strict = true, overwrite = false }) {
889 try {
890 this.use(source, data, folder, { strict, overwrite });
891 }
892 catch (error) {
893 error.message += ` (in ${source})`;
894 throw error;
895 }
896 }
897 use(source, data, folder, { strict = true, overwrite = false }) {
898 for (const key of Object.keys(data)) {
899 const value = data[key];
900 if (typeof value === `undefined`)
901 continue;
902 // The plugins have already been loaded at this point
903 if (key === `plugins`)
904 continue;
905 // Some environment variables should be ignored when applying the configuration
906 if (source === `<environment>` && IGNORED_ENV_VARIABLES.has(key))
907 continue;
908 // It wouldn't make much sense, would it?
909 if (key === `rcFilename`)
910 throw new clipanion_1.UsageError(`The rcFilename settings can only be set via ${`${exports.ENVIRONMENT_PREFIX}RC_FILENAME`.toUpperCase()}, not via a rc file`);
911 const definition = this.settings.get(key);
912 if (!definition) {
913 if (strict) {
914 throw new clipanion_1.UsageError(`Unrecognized or legacy configuration settings found: ${key} - run "yarn config -v" to see the list of settings supported in Yarn`);
915 }
916 else {
917 this.invalid.set(key, source);
918 continue;
919 }
920 }
921 if (this.sources.has(key) && !overwrite)
922 continue;
923 let parsed;
924 try {
925 parsed = parseValue(this, key, data[key], definition, folder);
926 }
927 catch (error) {
928 error.message += ` in ${source}`;
929 throw error;
930 }
931 this.values.set(key, parsed);
932 this.sources.set(key, source);
933 }
934 }
935 get(key) {
936 if (!this.values.has(key))
937 throw new Error(`Invalid configuration key "${key}"`);
938 return this.values.get(key);
939 }
940 getSpecial(key, { hideSecrets = false, getNativePaths = false }) {
941 const rawValue = this.get(key);
942 const definition = this.settings.get(key);
943 if (typeof definition === `undefined`)
944 throw new clipanion_1.UsageError(`Couldn't find a configuration settings named "${key}"`);
945 return transformConfiguration(rawValue, definition, {
946 hideSecrets,
947 getNativePaths,
948 });
949 }
950 getSubprocessStreams(logFile, { header, prefix, report }) {
951 let stdout;
952 let stderr;
953 const logStream = fslib_2.xfs.createWriteStream(logFile);
954 if (this.get(`enableInlineBuilds`)) {
955 const stdoutLineReporter = report.createStreamReporter(`${prefix} ${this.format(`STDOUT`, `green`)}`);
956 const stderrLineReporter = report.createStreamReporter(`${prefix} ${this.format(`STDERR`, `red`)}`);
957 stdout = new stream_1.PassThrough();
958 stdout.pipe(stdoutLineReporter);
959 stdout.pipe(logStream);
960 stderr = new stream_1.PassThrough();
961 stderr.pipe(stderrLineReporter);
962 stderr.pipe(logStream);
963 }
964 else {
965 stdout = logStream;
966 stderr = logStream;
967 if (typeof header !== `undefined`) {
968 stdout.write(`${header}\n`);
969 }
970 }
971 return { stdout, stderr };
972 }
973 makeResolver() {
974 const pluginResolvers = [];
975 for (const plugin of this.plugins.values())
976 for (const resolver of plugin.resolvers || [])
977 pluginResolvers.push(new resolver());
978 return new MultiResolver_1.MultiResolver([
979 new VirtualResolver_1.VirtualResolver(),
980 new WorkspaceResolver_1.WorkspaceResolver(),
981 new ProtocolResolver_1.ProtocolResolver(),
982 ...pluginResolvers,
983 ]);
984 }
985 makeFetcher() {
986 const pluginFetchers = [];
987 for (const plugin of this.plugins.values())
988 for (const fetcher of plugin.fetchers || [])
989 pluginFetchers.push(new fetcher());
990 return new MultiFetcher_1.MultiFetcher([
991 new VirtualFetcher_1.VirtualFetcher(),
992 new WorkspaceFetcher_1.WorkspaceFetcher(),
993 ...pluginFetchers,
994 ]);
995 }
996 getLinkers() {
997 const linkers = [];
998 for (const plugin of this.plugins.values())
999 for (const linker of plugin.linkers || [])
1000 linkers.push(new linker());
1001 return linkers;
1002 }
1003 async refreshPackageExtensions() {
1004 this.packageExtensions = new Map();
1005 const packageExtensions = this.packageExtensions;
1006 const registerPackageExtension = (descriptor, extensionData) => {
1007 if (!semver_1.default.validRange(descriptor.range))
1008 throw new Error(`Only semver ranges are allowed as keys for the lockfileExtensions setting`);
1009 const extension = new Manifest_1.Manifest();
1010 extension.load(extensionData);
1011 miscUtils.getArrayWithDefault(packageExtensions, descriptor.identHash).push({
1012 descriptor,
1013 changes: new Set([
1014 ...[
1015 ...extension.dependencies.values(),
1016 ...extension.peerDependencies.values(),
1017 ].map(descriptor => {
1018 return structUtils.stringifyIdent(descriptor);
1019 }),
1020 ...extension.dependenciesMeta.keys(),
1021 ...extension.peerDependenciesMeta.keys(),
1022 ]),
1023 patch: pkg => {
1024 pkg.dependencies = new Map([...pkg.dependencies, ...extension.dependencies]);
1025 pkg.peerDependencies = new Map([...pkg.peerDependencies, ...extension.peerDependencies]);
1026 pkg.dependenciesMeta = new Map([...pkg.dependenciesMeta, ...extension.dependenciesMeta]);
1027 pkg.peerDependenciesMeta = new Map([...pkg.peerDependenciesMeta, ...extension.peerDependenciesMeta]);
1028 },
1029 });
1030 };
1031 for (const [descriptorString, extensionData] of this.get(`packageExtensions`))
1032 registerPackageExtension(structUtils.parseDescriptor(descriptorString, true), extensionData);
1033 await this.triggerHook(hooks => {
1034 return hooks.registerPackageExtensions;
1035 }, this, registerPackageExtension);
1036 }
1037 normalizePackage(original) {
1038 const pkg = structUtils.copyPackage(original);
1039 // We use the extensions to define additional dependencies that weren't
1040 // properly listed in the original package definition
1041 if (this.packageExtensions == null)
1042 throw new Error(`refreshPackageExtensions has to be called before normalizing packages`);
1043 const extensionList = this.packageExtensions.get(original.identHash);
1044 if (typeof extensionList !== `undefined`) {
1045 const version = original.version;
1046 if (version !== null) {
1047 const extensionEntry = extensionList.find(({ descriptor }) => {
1048 return semverUtils.satisfiesWithPrereleases(version, descriptor.range);
1049 });
1050 if (typeof extensionEntry !== `undefined`) {
1051 extensionEntry.patch(pkg);
1052 }
1053 }
1054 }
1055 // We also add implicit optional @types peer dependencies for each peer
1056 // dependency. This is for compatibility reason, as many existing packages
1057 // forget to define their @types/react optional peer dependency when they
1058 // peer-depend on react.
1059 const getTypesName = (descriptor) => {
1060 return descriptor.scope
1061 ? `${descriptor.scope}__${descriptor.name}`
1062 : `${descriptor.name}`;
1063 };
1064 for (const descriptor of pkg.peerDependencies.values()) {
1065 if (descriptor.scope === `@types`)
1066 continue;
1067 const typesName = getTypesName(descriptor);
1068 const typesIdent = structUtils.makeIdent(`types`, typesName);
1069 if (pkg.peerDependencies.has(typesIdent.identHash) || pkg.peerDependenciesMeta.has(typesIdent.identHash))
1070 continue;
1071 pkg.peerDependenciesMeta.set(structUtils.stringifyIdent(typesIdent), {
1072 optional: true,
1073 });
1074 }
1075 // I don't like implicit dependencies, but package authors are reluctant to
1076 // use optional peer dependencies because they would print warnings in older
1077 // npm releases.
1078 for (const identString of pkg.peerDependenciesMeta.keys()) {
1079 const ident = structUtils.parseIdent(identString);
1080 if (!pkg.peerDependencies.has(ident.identHash)) {
1081 pkg.peerDependencies.set(ident.identHash, structUtils.makeDescriptor(ident, `*`));
1082 }
1083 }
1084 // We sort the dependencies so that further iterations always occur in the
1085 // same order, regardless how the various registries formatted their output
1086 pkg.dependencies = new Map(miscUtils.sortMap(pkg.dependencies, ([, descriptor]) => structUtils.stringifyDescriptor(descriptor)));
1087 pkg.peerDependencies = new Map(miscUtils.sortMap(pkg.peerDependencies, ([, descriptor]) => structUtils.stringifyDescriptor(descriptor)));
1088 return pkg;
1089 }
1090 getLimit(key) {
1091 return miscUtils.getFactoryWithDefault(this.limits, key, () => {
1092 return p_limit_1.default(this.get(key));
1093 });
1094 }
1095 async triggerHook(get, ...args) {
1096 for (const plugin of this.plugins.values()) {
1097 const hooks = plugin.hooks;
1098 if (!hooks)
1099 continue;
1100 const hook = get(hooks);
1101 if (!hook)
1102 continue;
1103 await hook(...args);
1104 }
1105 }
1106 async triggerMultipleHooks(get, argsList) {
1107 for (const args of argsList) {
1108 await this.triggerHook(get, ...args);
1109 }
1110 }
1111 async reduceHook(get, initialValue, ...args) {
1112 let value = initialValue;
1113 for (const plugin of this.plugins.values()) {
1114 const hooks = plugin.hooks;
1115 if (!hooks)
1116 continue;
1117 const hook = get(hooks);
1118 if (!hook)
1119 continue;
1120 value = await hook(value, ...args);
1121 }
1122 return value;
1123 }
1124 async firstHook(get, ...args) {
1125 for (const plugin of this.plugins.values()) {
1126 const hooks = plugin.hooks;
1127 if (!hooks)
1128 continue;
1129 const hook = get(hooks);
1130 if (!hook)
1131 continue;
1132 const ret = await hook(...args);
1133 if (typeof ret !== `undefined`) {
1134 // @ts-expect-error
1135 return ret;
1136 }
1137 }
1138 return null;
1139 }
1140 format(text, colorRequest) {
1141 if (colorRequest === FormatType.PATH)
1142 text = fslib_2.npath.fromPortablePath(text);
1143 if (!this.get(`enableColors`))
1144 return text;
1145 let color = exports.formatColors.get(colorRequest);
1146 if (typeof color === `undefined`)
1147 color = colorRequest;
1148 const fn = typeof color === `number`
1149 ? chalkInstance.ansi256(color)
1150 : color.startsWith(`#`)
1151 ? chalkInstance.hex(color)
1152 : chalkInstance[color];
1153 return fn(text);
1154 }
1155}
1156exports.Configuration = Configuration;
1157Configuration.telemetry = null;