UNPKG

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