"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Configuration = exports.coreDefinitions = exports.WindowsLinkType = exports.FormatType = exports.SettingsType = exports.SECRET = exports.DEFAULT_RC_FILENAME = exports.ENVIRONMENT_PREFIX = exports.TAG_REGEXP = exports.LEGACY_PLUGINS = void 0; const tslib_1 = require("tslib"); const fslib_1 = require("@yarnpkg/fslib"); const parsers_1 = require("@yarnpkg/parsers"); const camelcase_1 = tslib_1.__importDefault(require("camelcase")); const ci_info_1 = require("ci-info"); const clipanion_1 = require("clipanion"); const dotenv_1 = require("dotenv"); const module_1 = require("module"); const p_limit_1 = tslib_1.__importDefault(require("p-limit")); const stream_1 = require("stream"); const CorePlugin_1 = require("./CorePlugin"); const Manifest_1 = require("./Manifest"); const MultiFetcher_1 = require("./MultiFetcher"); const MultiResolver_1 = require("./MultiResolver"); const VirtualFetcher_1 = require("./VirtualFetcher"); const VirtualResolver_1 = require("./VirtualResolver"); const WorkspaceFetcher_1 = require("./WorkspaceFetcher"); const WorkspaceResolver_1 = require("./WorkspaceResolver"); const configUtils = tslib_1.__importStar(require("./configUtils")); const folderUtils = tslib_1.__importStar(require("./folderUtils")); const formatUtils = tslib_1.__importStar(require("./formatUtils")); const hashUtils = tslib_1.__importStar(require("./hashUtils")); const httpUtils = tslib_1.__importStar(require("./httpUtils")); const miscUtils = tslib_1.__importStar(require("./miscUtils")); const nodeUtils = tslib_1.__importStar(require("./nodeUtils")); const semverUtils = tslib_1.__importStar(require("./semverUtils")); const structUtils = tslib_1.__importStar(require("./structUtils")); const types_1 = require("./types"); const isPublicRepository = (function () { if (!ci_info_1.GITHUB_ACTIONS || !process.env.GITHUB_EVENT_PATH) return false; const githubEventPath = fslib_1.npath.toPortablePath(process.env.GITHUB_EVENT_PATH); let data; try { data = fslib_1.xfs.readJsonSync(githubEventPath); } catch { return false; } if (!(`repository` in data) || !data.repository) return false; if (data.repository.private ?? true) return false; return true; })(); exports.LEGACY_PLUGINS = new Set([ `@yarnpkg/plugin-constraints`, `@yarnpkg/plugin-exec`, `@yarnpkg/plugin-interactive-tools`, `@yarnpkg/plugin-stage`, `@yarnpkg/plugin-typescript`, `@yarnpkg/plugin-version`, `@yarnpkg/plugin-workspace-tools`, ]); const IGNORED_ENV_VARIABLES = new Set([ // Used by our test environment `isTestEnv`, `injectNpmUser`, `injectNpmPassword`, `injectNpm2FaToken`, `zipDataEpilogue`, `cacheCheckpointOverride`, `cacheVersionOverride`, `lockfileVersionOverride`, // "binFolder" is the magic location where the parent process stored the // current binaries; not an actual configuration settings `binFolder`, // "version" is set by Docker: // https://github.com/nodejs/docker-node/blob/5a6a5e91999358c5b04fddd6c22a9a4eb0bf3fbf/10/alpine/Dockerfile#L51 `version`, // "flags" is set by Netlify; they use it to specify the flags to send to the // CLI when running the automatic `yarn install` `flags`, // "gpg" and "profile" are used by the install.sh script: // https://classic.yarnpkg.com/install.sh `profile`, `gpg`, // "ignoreNode" is used to disable the Node version check `ignoreNode`, // "wrapOutput" was a variable used to indicate nested "yarn run" processes // back in Yarn 1. `wrapOutput`, // "YARN_HOME" and "YARN_CONF_DIR" may be present as part of the unrelated "Apache Hadoop YARN" software project. // https://hadoop.apache.org/docs/r0.23.11/hadoop-project-dist/hadoop-common/SingleCluster.html `home`, `confDir`, // "YARN_REGISTRY", read by yarn 1.x, prevents yarn 2+ installations if set `registry`, // "ignoreCwd" was previously used to skip extra chdir calls in Yarn Modern when `--cwd` was used. // It needs to be ignored because it's set by the parent process which could be anything. `ignoreCwd`, ]); exports.TAG_REGEXP = /^(?!v)[a-z0-9._-]+$/i; exports.ENVIRONMENT_PREFIX = `yarn_`; exports.DEFAULT_RC_FILENAME = `.yarnrc.yml`; exports.SECRET = `********`; var SettingsType; (function (SettingsType) { SettingsType["ANY"] = "ANY"; SettingsType["BOOLEAN"] = "BOOLEAN"; SettingsType["ABSOLUTE_PATH"] = "ABSOLUTE_PATH"; SettingsType["LOCATOR"] = "LOCATOR"; SettingsType["LOCATOR_LOOSE"] = "LOCATOR_LOOSE"; SettingsType["NUMBER"] = "NUMBER"; SettingsType["STRING"] = "STRING"; SettingsType["SECRET"] = "SECRET"; SettingsType["SHAPE"] = "SHAPE"; SettingsType["MAP"] = "MAP"; })(SettingsType || (exports.SettingsType = SettingsType = {})); /** * @deprecated Use {@link formatUtils.Type} */ exports.FormatType = formatUtils.Type; var WindowsLinkType; (function (WindowsLinkType) { WindowsLinkType["JUNCTIONS"] = "junctions"; WindowsLinkType["SYMLINKS"] = "symlinks"; })(WindowsLinkType || (exports.WindowsLinkType = WindowsLinkType = {})); // General rules: // // - filenames that don't accept actual paths must end with the "Filename" suffix // prefer to use absolute paths instead, since they are automatically resolved // // - folders must end with the "Folder" suffix // ex: cacheFolder, pnpVirtualFolder // // - actual paths to a file must end with the "Path" suffix // ex: pnpPath // // - options that tweaks the strictness must begin with the "allow" prefix // ex: allowInvalidChecksums // // - options that enable a feature must begin with the "enable" prefix // ex: enableEmojis, enableColors exports.coreDefinitions = { // 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 lastUpdateCheck: { description: `Last timestamp we checked whether new Yarn versions were available`, type: SettingsType.STRING, default: null, }, // Settings related to proxying all Yarn calls to a specific executable yarnPath: { description: `Path to the local executable that must be used over the global one`, type: SettingsType.ABSOLUTE_PATH, default: null, }, ignorePath: { description: `If true, the local executable will be ignored when using the global one`, type: SettingsType.BOOLEAN, default: false, }, // Settings related to the package manager internal names globalFolder: { description: `Folder where all system-global files are stored`, type: SettingsType.ABSOLUTE_PATH, default: folderUtils.getDefaultGlobalFolder(), }, cacheFolder: { description: `Folder where the cache files must be written`, type: SettingsType.ABSOLUTE_PATH, default: `./.yarn/cache`, }, compressionLevel: { 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)`, type: SettingsType.NUMBER, values: [`mixed`, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], default: 0, }, virtualFolder: { description: `Folder where the virtual packages (cf doc) will be mapped on the disk (must be named __virtual__)`, type: SettingsType.ABSOLUTE_PATH, default: `./.yarn/__virtual__`, }, installStatePath: { description: `Path of the file where the install state will be persisted`, type: SettingsType.ABSOLUTE_PATH, default: `./.yarn/install-state.gz`, }, immutablePatterns: { description: `Array of glob patterns; files matching them won't be allowed to change during immutable installs`, type: SettingsType.STRING, default: [], isArray: true, }, rcFilename: { description: `Name of the files where the configuration can be found`, type: SettingsType.STRING, default: getRcFilename(), }, enableGlobalCache: { description: `If true, the system-wide cache folder will be used regardless of \`cache-folder\``, type: SettingsType.BOOLEAN, default: true, }, cacheMigrationMode: { description: `Defines the conditions under which Yarn upgrades should cause the cache archives to be regenerated.`, type: SettingsType.STRING, values: [`always`, `match-spec`, `required-only`], default: `always`, }, // Settings related to the output style enableColors: { description: `If true, the CLI is allowed to use colors in its output`, type: SettingsType.BOOLEAN, default: formatUtils.supportsColor, defaultText: ``, }, enableHyperlinks: { description: `If true, the CLI is allowed to use hyperlinks in its output`, type: SettingsType.BOOLEAN, default: formatUtils.supportsHyperlinks, defaultText: ``, }, enableInlineBuilds: { description: `If true, the CLI will print the build output on the command line`, type: SettingsType.BOOLEAN, default: ci_info_1.isCI, defaultText: ``, }, enableMessageNames: { description: `If true, the CLI will prefix most messages with codes suitable for search engines`, type: SettingsType.BOOLEAN, default: true, }, enableProgressBars: { description: `If true, the CLI is allowed to show a progress bar for long-running events`, type: SettingsType.BOOLEAN, default: !ci_info_1.isCI, defaultText: ``, }, enableTimers: { description: `If true, the CLI is allowed to print the time spent executing commands`, type: SettingsType.BOOLEAN, default: true, }, enableTips: { description: `If true, installs will print a helpful message every day of the week`, type: SettingsType.BOOLEAN, default: !ci_info_1.isCI, defaultText: ``, }, /** * @internal Prefer using `Configuration#isInteractive`. */ preferInteractive: { description: `If true, the CLI will automatically use the interactive mode when called from a TTY`, type: SettingsType.BOOLEAN, default: false, }, preferTruncatedLines: { description: `If true, the CLI will truncate lines that would go beyond the size of the terminal`, type: SettingsType.BOOLEAN, default: false, }, progressBarStyle: { description: `Which style of progress bar should be used (only when progress bars are enabled)`, type: SettingsType.STRING, default: undefined, defaultText: ``, }, // Settings related to how packages are interpreted by default defaultLanguageName: { description: `Default language mode that should be used when a package doesn't offer any insight`, type: SettingsType.STRING, default: `node`, }, defaultProtocol: { description: `Default resolution protocol used when resolving pure semver and tag ranges`, type: SettingsType.STRING, default: `npm:`, }, enableTransparentWorkspaces: { description: `If false, Yarn won't automatically resolve workspace dependencies unless they use the \`workspace:\` protocol`, type: SettingsType.BOOLEAN, default: true, }, supportedArchitectures: { description: `Architectures that Yarn will fetch and inject into the resolver`, type: SettingsType.SHAPE, properties: { os: { description: `Array of supported process.platform strings, or null to target them all`, type: SettingsType.STRING, isArray: true, isNullable: true, default: [`current`], }, cpu: { description: `Array of supported process.arch strings, or null to target them all`, type: SettingsType.STRING, isArray: true, isNullable: true, default: [`current`], }, libc: { description: `Array of supported libc libraries, or null to target them all`, type: SettingsType.STRING, isArray: true, isNullable: true, default: [`current`], }, }, }, // Settings related to network access enableMirror: { description: `If true, the downloaded packages will be retrieved and stored in both the local and global folders`, type: SettingsType.BOOLEAN, default: true, }, enableNetwork: { description: `If false, Yarn will refuse to use the network if required to`, type: SettingsType.BOOLEAN, default: true, }, enableOfflineMode: { description: `If true, Yarn will attempt to retrieve files and metadata from the global cache rather than the network`, type: SettingsType.BOOLEAN, default: false, }, httpProxy: { description: `URL of the http proxy that must be used for outgoing http requests`, type: SettingsType.STRING, default: null, }, httpsProxy: { description: `URL of the http proxy that must be used for outgoing https requests`, type: SettingsType.STRING, default: null, }, unsafeHttpWhitelist: { description: `List of the hostnames for which http queries are allowed (glob patterns are supported)`, type: SettingsType.STRING, default: [], isArray: true, }, httpTimeout: { description: `Timeout of each http request in milliseconds`, type: SettingsType.NUMBER, default: 60000, }, httpRetry: { description: `Retry times on http failure`, type: SettingsType.NUMBER, default: 3, }, networkConcurrency: { description: `Maximal number of concurrent requests`, type: SettingsType.NUMBER, default: 50, }, taskPoolConcurrency: { description: `Maximal amount of concurrent heavy task processing`, type: SettingsType.NUMBER, default: nodeUtils.availableParallelism(), }, taskPoolMode: { description: `Execution strategy for heavy tasks`, type: SettingsType.STRING, values: [`async`, `workers`], default: `workers`, }, networkSettings: { description: `Network settings per hostname (glob patterns are supported)`, type: SettingsType.MAP, valueDefinition: { description: ``, type: SettingsType.SHAPE, properties: { httpsCaFilePath: { description: `Path to file containing one or multiple Certificate Authority signing certificates`, type: SettingsType.ABSOLUTE_PATH, default: null, }, enableNetwork: { description: `If false, the package manager will refuse to use the network if required to`, type: SettingsType.BOOLEAN, default: null, }, httpProxy: { description: `URL of the http proxy that must be used for outgoing http requests`, type: SettingsType.STRING, default: null, }, httpsProxy: { description: `URL of the http proxy that must be used for outgoing https requests`, type: SettingsType.STRING, default: null, }, httpsKeyFilePath: { description: `Path to file containing private key in PEM format`, type: SettingsType.ABSOLUTE_PATH, default: null, }, httpsCertFilePath: { description: `Path to file containing certificate chain in PEM format`, type: SettingsType.ABSOLUTE_PATH, default: null, }, }, }, }, httpsCaFilePath: { description: `A path to a file containing one or multiple Certificate Authority signing certificates`, type: SettingsType.ABSOLUTE_PATH, default: null, }, httpsKeyFilePath: { description: `Path to file containing private key in PEM format`, type: SettingsType.ABSOLUTE_PATH, default: null, }, httpsCertFilePath: { description: `Path to file containing certificate chain in PEM format`, type: SettingsType.ABSOLUTE_PATH, default: null, }, enableStrictSsl: { description: `If false, SSL certificate errors will be ignored`, type: SettingsType.BOOLEAN, default: true, }, logFilters: { description: `Overrides for log levels`, type: SettingsType.SHAPE, isArray: true, concatenateValues: true, properties: { code: { description: `Code of the messages covered by this override`, type: SettingsType.STRING, default: undefined, }, text: { description: `Code of the texts covered by this override`, type: SettingsType.STRING, default: undefined, }, pattern: { description: `Code of the patterns covered by this override`, type: SettingsType.STRING, default: undefined, }, level: { description: `Log level override, set to null to remove override`, type: SettingsType.STRING, values: Object.values(formatUtils.LogLevel), isNullable: true, default: undefined, }, }, }, // Settings related to telemetry enableTelemetry: { description: `If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry`, type: SettingsType.BOOLEAN, default: true, }, telemetryInterval: { description: `Minimal amount of time between two telemetry uploads, in days`, type: SettingsType.NUMBER, default: 7, }, telemetryUserId: { description: `If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.`, type: SettingsType.STRING, default: null, }, // Settings related to security enableHardenedMode: { description: `If true, automatically enable --check-resolutions --refresh-lockfile on installs`, type: SettingsType.BOOLEAN, default: ci_info_1.isPR && isPublicRepository, defaultText: ``, }, enableScripts: { description: `If true, packages are allowed to have install scripts by default`, type: SettingsType.BOOLEAN, default: true, }, enableStrictSettings: { description: `If true, unknown settings will cause Yarn to abort`, type: SettingsType.BOOLEAN, default: true, }, enableImmutableCache: { description: `If true, the cache is reputed immutable and actions that would modify it will throw`, type: SettingsType.BOOLEAN, default: false, }, enableCacheClean: { description: `If false, disallows the \`cache clean\` command`, type: SettingsType.BOOLEAN, default: true, }, checksumBehavior: { description: `Enumeration defining what to do when a checksum doesn't match expectations`, type: SettingsType.STRING, default: `throw`, }, // Miscellaneous settings injectEnvironmentFiles: { description: `List of all the environment files that Yarn should inject inside the process when it starts`, type: SettingsType.ABSOLUTE_PATH, default: [`.env.yarn?`], isArray: true, }, // Package patching - to fix incorrect definitions packageExtensions: { description: `Map of package corrections to apply on the dependency tree`, type: SettingsType.MAP, valueDefinition: { description: `The extension that will be applied to any package whose version matches the specified range`, type: SettingsType.SHAPE, properties: { dependencies: { description: `The set of dependencies that must be made available to the current package in order for it to work properly`, type: SettingsType.MAP, valueDefinition: { description: `A range`, type: SettingsType.STRING, }, }, peerDependencies: { description: `Inherited dependencies - the consumer of the package will be tasked to provide them`, type: SettingsType.MAP, valueDefinition: { description: `A semver range`, type: SettingsType.STRING, }, }, peerDependenciesMeta: { description: `Extra information related to the dependencies listed in the peerDependencies field`, type: SettingsType.MAP, valueDefinition: { description: `The peerDependency meta`, type: SettingsType.SHAPE, properties: { optional: { description: `If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error`, type: SettingsType.BOOLEAN, default: false, }, }, }, }, }, }, }, }; // There are two types of values // 1. ResolvedRcFile from `configUtils.resolveRcFiles` // 2. objects passed directly via `configuration.useWithSource` or `configuration.use` function parseValue(configuration, path, valueBase, definition, folder) { const value = configUtils.getValue(valueBase); if (definition.isArray || (definition.type === SettingsType.ANY && Array.isArray(value))) { if (!Array.isArray(value)) { return String(value).split(/,/).map(segment => { return parseSingleValue(configuration, path, segment, definition, folder); }); } else { return value.map((sub, i) => parseSingleValue(configuration, `${path}[${i}]`, sub, definition, folder)); } } else { if (Array.isArray(value)) { throw new Error(`Non-array configuration settings "${path}" cannot be an array`); } else { return parseSingleValue(configuration, path, valueBase, definition, folder); } } } function parseSingleValue(configuration, path, valueBase, definition, folder) { const value = configUtils.getValue(valueBase); switch (definition.type) { case SettingsType.ANY: return configUtils.getValueByTree(value); case SettingsType.SHAPE: return parseShape(configuration, path, valueBase, definition, folder); case SettingsType.MAP: return parseMap(configuration, path, valueBase, definition, folder); } if (value === null && !definition.isNullable && definition.default !== null) throw new Error(`Non-nullable configuration settings "${path}" cannot be set to null`); if (definition.values?.includes(value)) return value; const interpretValue = () => { if (definition.type === SettingsType.BOOLEAN && typeof value !== `string`) return miscUtils.parseBoolean(value); if (typeof value !== `string`) throw new Error(`Expected configuration setting "${path}" to be a string, got ${typeof value}`); const valueWithReplacedVariables = miscUtils.replaceEnvVariables(value, { env: configuration.env, }); switch (definition.type) { case SettingsType.ABSOLUTE_PATH: { let cwd = folder; // singleValue's source should be a single file path, if it exists const source = configUtils.getSource(valueBase); if (source && source[0] !== `<`) cwd = fslib_1.ppath.dirname(source); return fslib_1.ppath.resolve(cwd, fslib_1.npath.toPortablePath(valueWithReplacedVariables)); } case SettingsType.LOCATOR_LOOSE: return structUtils.parseLocator(valueWithReplacedVariables, false); case SettingsType.NUMBER: return parseInt(valueWithReplacedVariables); case SettingsType.LOCATOR: return structUtils.parseLocator(valueWithReplacedVariables); case SettingsType.BOOLEAN: return miscUtils.parseBoolean(valueWithReplacedVariables); default: return valueWithReplacedVariables; } }; const interpreted = interpretValue(); if (definition.values && !definition.values.includes(interpreted)) throw new Error(`Invalid value, expected one of ${definition.values.join(`, `)}`); return interpreted; } function parseShape(configuration, path, valueBase, definition, folder) { const value = configUtils.getValue(valueBase); if (typeof value !== `object` || Array.isArray(value)) throw new clipanion_1.UsageError(`Object configuration settings "${path}" must be an object`); const result = getDefaultValue(configuration, definition, { ignoreArrays: true, }); if (value === null) return result; for (const [propKey, propValue] of Object.entries(value)) { const subPath = `${path}.${propKey}`; const subDefinition = definition.properties[propKey]; if (!subDefinition) throw new clipanion_1.UsageError(`Unrecognized configuration settings found: ${path}.${propKey} - run "yarn config -v" to see the list of settings supported in Yarn`); result.set(propKey, parseValue(configuration, subPath, propValue, definition.properties[propKey], folder)); } return result; } function parseMap(configuration, path, valueBase, definition, folder) { const value = configUtils.getValue(valueBase); const result = new Map(); if (typeof value !== `object` || Array.isArray(value)) throw new clipanion_1.UsageError(`Map configuration settings "${path}" must be an object`); if (value === null) return result; for (const [propKey, propValue] of Object.entries(value)) { const normalizedKey = definition.normalizeKeys ? definition.normalizeKeys(propKey) : propKey; const subPath = `${path}['${normalizedKey}']`; // @ts-expect-error: SettingsDefinitionNoDefault has ... no default ... but // that's fine because we're guaranteed it's not undefined. const valueDefinition = definition.valueDefinition; result.set(normalizedKey, parseValue(configuration, subPath, propValue, valueDefinition, folder)); } return result; } function getDefaultValue(configuration, definition, { ignoreArrays = false } = {}) { switch (definition.type) { case SettingsType.SHAPE: { if (definition.isArray && !ignoreArrays) return []; const result = new Map(); for (const [propKey, propDefinition] of Object.entries(definition.properties)) result.set(propKey, getDefaultValue(configuration, propDefinition)); return result; } case SettingsType.MAP: { if (definition.isArray && !ignoreArrays) return []; return new Map(); } case SettingsType.ABSOLUTE_PATH: { if (definition.default === null) return null; if (configuration.projectCwd === null) { if (Array.isArray(definition.default)) { return definition.default.map((entry) => fslib_1.ppath.normalize(entry)); } else if (fslib_1.ppath.isAbsolute(definition.default)) { return fslib_1.ppath.normalize(definition.default); } else if (definition.isNullable) { return null; } else { // Reached when a relative path is the default but the current // context is evaluated outside of a Yarn project return undefined; } } else { if (Array.isArray(definition.default)) { return definition.default.map((entry) => fslib_1.ppath.resolve(configuration.projectCwd, entry)); } else { return fslib_1.ppath.resolve(configuration.projectCwd, definition.default); } } } default: { return definition.default; } } } function transformConfiguration(rawValue, definition, transforms) { if (definition.type === SettingsType.SECRET && typeof rawValue === `string` && transforms.hideSecrets) return exports.SECRET; if (definition.type === SettingsType.ABSOLUTE_PATH && typeof rawValue === `string` && transforms.getNativePaths) return fslib_1.npath.fromPortablePath(rawValue); if (definition.isArray && Array.isArray(rawValue)) { const newValue = []; for (const value of rawValue) newValue.push(transformConfiguration(value, definition, transforms)); return newValue; } if (definition.type === SettingsType.MAP && rawValue instanceof Map) { if (rawValue.size === 0) return undefined; const newValue = new Map(); for (const [key, value] of rawValue.entries()) { const transformedValue = transformConfiguration(value, definition.valueDefinition, transforms); if (typeof transformedValue !== `undefined`) { newValue.set(key, transformedValue); } } return newValue; } if (definition.type === SettingsType.SHAPE && rawValue instanceof Map) { if (rawValue.size === 0) return undefined; const newValue = new Map(); for (const [key, value] of rawValue.entries()) { const propertyDefinition = definition.properties[key]; const transformedValue = transformConfiguration(value, propertyDefinition, transforms); if (typeof transformedValue !== `undefined`) { newValue.set(key, transformedValue); } } return newValue; } return rawValue; } function getEnvironmentSettings() { const environmentSettings = {}; for (let [key, value] of Object.entries(process.env)) { key = key.toLowerCase(); if (!key.startsWith(exports.ENVIRONMENT_PREFIX)) continue; key = (0, camelcase_1.default)(key.slice(exports.ENVIRONMENT_PREFIX.length)); environmentSettings[key] = value; } return environmentSettings; } function getRcFilename() { const rcKey = `${exports.ENVIRONMENT_PREFIX}rc_filename`; for (const [key, value] of Object.entries(process.env)) if (key.toLowerCase() === rcKey && typeof value === `string`) return value; return exports.DEFAULT_RC_FILENAME; } async function tryRead(p) { try { return await fslib_1.xfs.readFilePromise(p); } catch { return Buffer.of(); } } async function isSameBinaryContent(a, b) { return Buffer.compare(...await Promise.all([ tryRead(a), tryRead(b), ])) === 0; } async function isSameBinaryInode(a, b) { const [aStat, bStat] = await Promise.all([ fslib_1.xfs.statPromise(a), fslib_1.xfs.statPromise(b), ]); return aStat.dev === bStat.dev && aStat.ino === bStat.ino; } const isSameBinary = process.platform === `win32` ? isSameBinaryContent : isSameBinaryInode; async function checkYarnPath({ configuration, selfPath }) { const yarnPath = configuration.get(`yarnPath`); const ignorePath = configuration.get(`ignorePath`); if (ignorePath || yarnPath === null || yarnPath === selfPath) return null; if (await isSameBinary(yarnPath, selfPath)) return null; return yarnPath; } class Configuration { static create(startingCwd, projectCwdOrPlugins, maybePlugins) { const configuration = new Configuration(startingCwd); if (typeof projectCwdOrPlugins !== `undefined` && !(projectCwdOrPlugins instanceof Map)) configuration.projectCwd = projectCwdOrPlugins; configuration.importSettings(exports.coreDefinitions); const plugins = typeof maybePlugins !== `undefined` ? maybePlugins : projectCwdOrPlugins instanceof Map ? projectCwdOrPlugins : new Map(); for (const [name, plugin] of plugins) configuration.activatePlugin(name, plugin); return configuration; } /** * Instantiate a new configuration object exposing the configuration obtained * from reading the various rc files and the environment settings. * * The `pluginConfiguration` parameter is expected to indicate: * * 1. which modules should be made available to plugins when they require a * package (this is the dynamic linking part - for example we want all the * plugins to use the exact same version of @yarnpkg/core, which also is the * version used by the running Yarn instance). * * 2. which of those modules are actually plugins that need to be injected * within the configuration. * * Note that some extra plugins will be automatically added based on the * content of the rc files - with the rc plugins taking precedence over * the other ones. * * One particularity: the plugin initialization order is quite strict, with * plugins listed in /foo/bar/.yarnrc.yml taking precedence over plugins * listed in /foo/.yarnrc.yml and /.yarnrc.yml. Additionally, while plugins * can depend on one another, they can only depend on plugins that have been * instantiated before them (so a plugin listed in /foo/.yarnrc.yml can * depend on another one listed on /foo/bar/.yarnrc.yml, but not the other * way around). */ static async find(startingCwd, pluginConfiguration, { strict = true, usePathCheck = null, useRc = true } = {}) { const environmentSettings = getEnvironmentSettings(); delete environmentSettings.rcFilename; const configuration = new Configuration(startingCwd); const rcFiles = await Configuration.findRcFiles(startingCwd); const homeRcFile = await Configuration.findFolderRcFile(folderUtils.getHomeFolder()); if (homeRcFile) { const rcFile = rcFiles.find(rcFile => rcFile.path === homeRcFile.path); if (!rcFile) { rcFiles.unshift(homeRcFile); } } const resolvedRcFile = configUtils.resolveRcFiles(rcFiles.map(rcFile => [rcFile.path, rcFile.data])); // XXX: in fact, it is not useful, but in order not to change the parameters of useWithSource, temporarily put a thing to prevent errors. const resolvedRcFileCwd = fslib_1.PortablePath.dot; const allCoreFieldKeys = new Set(Object.keys(exports.coreDefinitions)); const pickPrimaryCoreFields = ({ yarnPath, ignorePath, injectEnvironmentFiles }) => ({ yarnPath, ignorePath, injectEnvironmentFiles }); const pickSecondaryCoreFields = ({ yarnPath, ignorePath, injectEnvironmentFiles, ...rest }) => { const secondaryCoreFields = {}; for (const [key, value] of Object.entries(rest)) if (allCoreFieldKeys.has(key)) secondaryCoreFields[key] = value; return secondaryCoreFields; }; const pickPluginFields = ({ yarnPath, ignorePath, ...rest }) => { const pluginFields = {}; for (const [key, value] of Object.entries(rest)) if (!allCoreFieldKeys.has(key)) pluginFields[key] = value; return pluginFields; }; configuration.importSettings(pickPrimaryCoreFields(exports.coreDefinitions)); configuration.useWithSource(``, pickPrimaryCoreFields(environmentSettings), startingCwd, { strict: false }); if (resolvedRcFile) { const [source, data] = resolvedRcFile; configuration.useWithSource(source, pickPrimaryCoreFields(data), resolvedRcFileCwd, { strict: false }); } if (usePathCheck) { const yarnPath = await checkYarnPath({ configuration, selfPath: usePathCheck, }); if (yarnPath !== null) { return configuration; } else { configuration.useWithSource(``, { ignorePath: true }, startingCwd, { strict: false, overwrite: true }); } } // We need to know the project root before being able to truly instantiate // our configuration. const projectCwd = await Configuration.findProjectCwd(startingCwd); // Great! We now have enough information to really start to setup the // core configuration object. configuration.startingCwd = startingCwd; configuration.projectCwd = projectCwd; const env = Object.assign(Object.create(null), process.env); configuration.env = env; // load the environment files const environmentFiles = await Promise.all(configuration.get(`injectEnvironmentFiles`).map(async (p) => { const content = p.endsWith(`?`) ? await fslib_1.xfs.readFilePromise(p.slice(0, -1), `utf8`).catch(() => ``) : await fslib_1.xfs.readFilePromise(p, `utf8`); return (0, dotenv_1.parse)(content); })); for (const environmentEntries of environmentFiles) for (const [key, value] of Object.entries(environmentEntries)) configuration.env[key] = miscUtils.replaceEnvVariables(value, { env }); // load all fields of the core definitions configuration.importSettings(pickSecondaryCoreFields(exports.coreDefinitions)); configuration.useWithSource(``, pickSecondaryCoreFields(environmentSettings), startingCwd, { strict }); if (resolvedRcFile) { const [source, data] = resolvedRcFile; configuration.useWithSource(source, pickSecondaryCoreFields(data), resolvedRcFileCwd, { strict }); } // Now that the configuration object is almost ready, we need to load all // the configured plugins const getDefault = (object) => { return `default` in object ? object.default : object; }; // load the core plugins const corePlugins = new Map([ [`@@core`, CorePlugin_1.CorePlugin], ]); if (pluginConfiguration !== null) for (const request of pluginConfiguration.plugins.keys()) corePlugins.set(request, getDefault(pluginConfiguration.modules.get(request))); for (const [name, corePlugin] of corePlugins) configuration.activatePlugin(name, corePlugin); // load third-party plugins const thirdPartyPlugins = new Map([]); if (pluginConfiguration !== null) { const requireEntries = new Map(); for (const [request, embedModule] of pluginConfiguration.modules) requireEntries.set(request, () => embedModule); const dynamicPlugins = new Set(); const importPlugin = async (pluginPath, source) => { const { factory, name } = miscUtils.dynamicRequire(pluginPath); if (!factory) return; // Prevent plugin redefinition so that the ones declared deeper in the // filesystem always have precedence over the ones below. if (dynamicPlugins.has(name)) return; const pluginRequireEntries = new Map(requireEntries); const pluginRequire = (request) => { if ((0, module_1.isBuiltin)(request)) return miscUtils.dynamicRequire(request); if (pluginRequireEntries.has(request)) { return pluginRequireEntries.get(request)(); } else { throw new clipanion_1.UsageError(`This plugin cannot access the package referenced via ${request} which is neither a builtin, nor an exposed entry`); } }; const plugin = await miscUtils.prettifyAsyncErrors(async () => { return getDefault(await factory(pluginRequire)); }, message => { return `${message} (when initializing ${name}, defined in ${source})`; }); requireEntries.set(name, () => plugin); dynamicPlugins.add(name); thirdPartyPlugins.set(name, plugin); }; if (environmentSettings.plugins) { for (const userProvidedPath of environmentSettings.plugins.split(`;`)) { const pluginPath = fslib_1.ppath.resolve(startingCwd, fslib_1.npath.toPortablePath(userProvidedPath)); await importPlugin(pluginPath, ``); } } for (const { path, cwd, data } of rcFiles) { if (!useRc) continue; if (!Array.isArray(data.plugins)) continue; for (const userPluginEntry of data.plugins) { const userProvidedPath = typeof userPluginEntry !== `string` ? userPluginEntry.path : userPluginEntry; const userProvidedSpec = userPluginEntry?.spec ?? ``; const userProvidedChecksum = userPluginEntry?.checksum ?? ``; if (exports.LEGACY_PLUGINS.has(userProvidedSpec)) continue; const pluginPath = fslib_1.ppath.resolve(cwd, fslib_1.npath.toPortablePath(userProvidedPath)); if (!await fslib_1.xfs.existsPromise(pluginPath)) { if (!userProvidedSpec) { const prettyPluginName = formatUtils.pretty(configuration, fslib_1.ppath.basename(pluginPath, `.cjs`), formatUtils.Type.NAME); const prettyGitIgnore = formatUtils.pretty(configuration, `.gitignore`, formatUtils.Type.NAME); const prettyYarnrc = formatUtils.pretty(configuration, configuration.values.get(`rcFilename`), formatUtils.Type.NAME); const prettyUrl = formatUtils.pretty(configuration, `https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored`, formatUtils.Type.URL); throw new clipanion_1.UsageError(`Missing source for the ${prettyPluginName} plugin - please try to remove the plugin from ${prettyYarnrc} then reinstall it manually. This error usually occurs because ${prettyGitIgnore} is incorrect, check ${prettyUrl} to make sure your plugin folder isn't gitignored.`); } if (!userProvidedSpec.match(/^https?:/)) { const prettyPluginName = formatUtils.pretty(configuration, fslib_1.ppath.basename(pluginPath, `.cjs`), formatUtils.Type.NAME); const prettyYarnrc = formatUtils.pretty(configuration, configuration.values.get(`rcFilename`), formatUtils.Type.NAME); throw new clipanion_1.UsageError(`Failed to recognize the source for the ${prettyPluginName} plugin - please try to delete the plugin from ${prettyYarnrc} then reinstall it manually.`); } const pluginBuffer = await httpUtils.get(userProvidedSpec, { configuration }); const pluginChecksum = hashUtils.makeHash(pluginBuffer); // if there is no checksum, this means that the user used --no-checksum and does not need to check this plugin if (userProvidedChecksum && userProvidedChecksum !== pluginChecksum) { const prettyPluginName = formatUtils.pretty(configuration, fslib_1.ppath.basename(pluginPath, `.cjs`), formatUtils.Type.NAME); const prettyYarnrc = formatUtils.pretty(configuration, configuration.values.get(`rcFilename`), formatUtils.Type.NAME); const prettyPluginImportCommand = formatUtils.pretty(configuration, `yarn plugin import ${userProvidedSpec}`, formatUtils.Type.CODE); throw new clipanion_1.UsageError(`Failed to fetch the ${prettyPluginName} plugin from its remote location: its checksum seems to have changed. If this is expected, please remove the plugin from ${prettyYarnrc} then run ${prettyPluginImportCommand} to reimport it.`); } await fslib_1.xfs.mkdirPromise(fslib_1.ppath.dirname(pluginPath), { recursive: true }); await fslib_1.xfs.writeFilePromise(pluginPath, pluginBuffer); } await importPlugin(pluginPath, path); } } } for (const [name, thirdPartyPlugin] of thirdPartyPlugins) configuration.activatePlugin(name, thirdPartyPlugin); // load values of all plugin definitions configuration.useWithSource(``, pickPluginFields(environmentSettings), startingCwd, { strict }); if (resolvedRcFile) { const [source, data] = resolvedRcFile; configuration.useWithSource(source, pickPluginFields(data), resolvedRcFileCwd, { strict }); } if (configuration.get(`enableGlobalCache`)) { configuration.values.set(`cacheFolder`, `${configuration.get(`globalFolder`)}/cache`); configuration.sources.set(`cacheFolder`, ``); } return configuration; } static async findRcFiles(startingCwd) { const rcFilename = getRcFilename(); const rcFiles = []; let nextCwd = startingCwd; let currentCwd = null; while (nextCwd !== currentCwd) { currentCwd = nextCwd; const rcPath = fslib_1.ppath.join(currentCwd, rcFilename); if (fslib_1.xfs.existsSync(rcPath)) { let data; let content; try { content = await fslib_1.xfs.readFilePromise(rcPath, `utf8`); data = (0, parsers_1.parseSyml)(content); } catch { let tip = ``; if (content?.match(/^\s+(?!-)[^:]+\s+\S+/m)) tip = ` (in particular, make sure you list the colons after each key name)`; throw new clipanion_1.UsageError(`Parse error when loading ${rcPath}; please check it's proper Yaml${tip}`); } rcFiles.unshift({ path: rcPath, cwd: currentCwd, data }); } nextCwd = fslib_1.ppath.dirname(currentCwd); } return rcFiles; } static async findFolderRcFile(cwd) { const path = fslib_1.ppath.join(cwd, fslib_1.Filename.rc); let content; try { content = await fslib_1.xfs.readFilePromise(path, `utf8`); } catch (err) { if (err.code === `ENOENT`) return null; throw err; } const data = (0, parsers_1.parseSyml)(content); return { path, cwd, data }; } static async findProjectCwd(startingCwd) { let projectCwd = null; let nextCwd = startingCwd; let currentCwd = null; while (nextCwd !== currentCwd) { currentCwd = nextCwd; if (fslib_1.xfs.existsSync(fslib_1.ppath.join(currentCwd, fslib_1.Filename.lockfile))) return currentCwd; if (fslib_1.xfs.existsSync(fslib_1.ppath.join(currentCwd, fslib_1.Filename.manifest))) projectCwd = currentCwd; nextCwd = fslib_1.ppath.dirname(currentCwd); } return projectCwd; } static async updateConfiguration(cwd, patch, opts = {}) { const rcFilename = getRcFilename(); const configurationPath = fslib_1.ppath.join(cwd, rcFilename); const current = fslib_1.xfs.existsSync(configurationPath) ? (0, parsers_1.parseSyml)(await fslib_1.xfs.readFilePromise(configurationPath, `utf8`)) : {}; let patched = false; let replacement; if (typeof patch === `function`) { try { replacement = patch(current); } catch { replacement = patch({}); } if (replacement === current) { return false; } } else { replacement = current; for (const key of Object.keys(patch)) { const currentValue = current[key]; const patchField = patch[key]; let nextValue; if (typeof patchField === `function`) { try { nextValue = patchField(currentValue); } catch { nextValue = patchField(undefined); } } else { nextValue = patchField; } if (currentValue === nextValue) continue; if (nextValue === Configuration.deleteProperty) delete replacement[key]; else replacement[key] = nextValue; patched = true; } if (!patched) { return false; } } await fslib_1.xfs.changeFilePromise(configurationPath, (0, parsers_1.stringifySyml)(replacement), { automaticNewlines: true, }); return true; } static async addPlugin(cwd, pluginMetaList) { if (pluginMetaList.length === 0) return; await Configuration.updateConfiguration(cwd, (current) => { const currentPluginMetaList = current.plugins ?? []; if (currentPluginMetaList.length === 0) return { ...current, plugins: pluginMetaList }; const newPluginMetaList = []; let notYetProcessedList = [...pluginMetaList]; for (const currentPluginMeta of currentPluginMetaList) { const currentPluginPath = typeof currentPluginMeta !== `string` ? currentPluginMeta.path : currentPluginMeta; const updatingPlugin = notYetProcessedList.find(pluginMeta => { return pluginMeta.path === currentPluginPath; }); if (updatingPlugin) { newPluginMetaList.push(updatingPlugin); notYetProcessedList = notYetProcessedList.filter(p => p !== updatingPlugin); } else { newPluginMetaList.push(currentPluginMeta); } } newPluginMetaList.push(...notYetProcessedList); return { ...current, plugins: newPluginMetaList }; }); } static async updateHomeConfiguration(patch) { const homeFolder = folderUtils.getHomeFolder(); return await Configuration.updateConfiguration(homeFolder, patch); } constructor(startingCwd) { this.isCI = ci_info_1.isCI; this.projectCwd = null; this.plugins = new Map(); this.settings = new Map(); this.values = new Map(); this.sources = new Map(); this.invalid = new Map(); this.env = {}; this.limits = new Map(); this.packageExtensions = null; this.startingCwd = startingCwd; } activatePlugin(name, plugin) { this.plugins.set(name, plugin); if (typeof plugin.configuration !== `undefined`) { this.importSettings(plugin.configuration); } } importSettings(definitions) { for (const [name, definition] of Object.entries(definitions)) { if (definition == null) continue; if (this.settings.has(name)) throw new Error(`Cannot redefine settings "${name}"`); this.settings.set(name, definition); this.values.set(name, getDefaultValue(this, definition)); } } useWithSource(source, data, folder, opts) { try { this.use(source, data, folder, opts); } catch (error) { error.message += ` (in ${formatUtils.pretty(this, source, formatUtils.Type.PATH)})`; throw error; } } use(source, data, folder, { strict = true, overwrite = false } = {}) { strict = strict && this.get(`enableStrictSettings`); for (const key of [`enableStrictSettings`, ...Object.keys(data)]) { const value = data[key]; const fieldSource = configUtils.getSource(value); if (fieldSource) source = fieldSource; if (typeof value === `undefined`) continue; // The plugins have already been loaded at this point if (key === `plugins`) continue; // Some environment variables should be ignored when applying the configuration if (source === `` && IGNORED_ENV_VARIABLES.has(key)) continue; // It wouldn't make much sense, would it? if (key === `rcFilename`) throw new clipanion_1.UsageError(`The rcFilename settings can only be set via ${`${exports.ENVIRONMENT_PREFIX}RC_FILENAME`.toUpperCase()}, not via a rc file`); const definition = this.settings.get(key); if (!definition) { const homeFolder = folderUtils.getHomeFolder(); const rcFileFolder = source[0] !== `<` ? fslib_1.ppath.dirname(source) : null; const isHomeRcFile = rcFileFolder !== null ? homeFolder === rcFileFolder : false; if (strict && !isHomeRcFile) { 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`); } else { this.invalid.set(key, source); continue; } } if (this.sources.has(key) && !(overwrite || definition.type === SettingsType.MAP || definition.isArray && definition.concatenateValues)) continue; let parsed; try { parsed = parseValue(this, key, value, definition, folder); } catch (error) { error.message += ` in ${formatUtils.pretty(this, source, formatUtils.Type.PATH)}`; throw error; } if (key === `enableStrictSettings` && source !== ``) { strict = parsed; continue; } if (definition.type === SettingsType.MAP) { const previousValue = this.values.get(key); this.values.set(key, new Map(overwrite ? [...previousValue, ...parsed] : [...parsed, ...previousValue])); this.sources.set(key, `${this.sources.get(key)}, ${source}`); } else if (definition.isArray && definition.concatenateValues) { const previousValue = this.values.get(key); this.values.set(key, overwrite ? [...previousValue, ...parsed] : [...parsed, ...previousValue]); this.sources.set(key, `${this.sources.get(key)}, ${source}`); } else { this.values.set(key, parsed); this.sources.set(key, source); } } } get(key) { if (!this.values.has(key)) throw new Error(`Invalid configuration key "${key}"`); return this.values.get(key); } getSpecial(key, { hideSecrets = false, getNativePaths = false }) { const rawValue = this.get(key); const definition = this.settings.get(key); if (typeof definition === `undefined`) throw new clipanion_1.UsageError(`Couldn't find a configuration settings named "${key}"`); return transformConfiguration(rawValue, definition, { hideSecrets, getNativePaths, }); } getSubprocessStreams(logFile, { header, prefix, report }) { let stdout; let stderr; const logStream = fslib_1.xfs.createWriteStream(logFile); if (this.get(`enableInlineBuilds`)) { const stdoutLineReporter = report.createStreamReporter(`${prefix} ${formatUtils.pretty(this, `STDOUT`, `green`)}`); const stderrLineReporter = report.createStreamReporter(`${prefix} ${formatUtils.pretty(this, `STDERR`, `red`)}`); stdout = new stream_1.PassThrough(); stdout.pipe(stdoutLineReporter); stdout.pipe(logStream); stderr = new stream_1.PassThrough(); stderr.pipe(stderrLineReporter); stderr.pipe(logStream); } else { stdout = logStream; stderr = logStream; if (typeof header !== `undefined`) { stdout.write(`${header}\n`); } } return { stdout, stderr }; } makeResolver() { const pluginResolvers = []; for (const plugin of this.plugins.values()) for (const resolver of plugin.resolvers || []) pluginResolvers.push(new resolver()); return (new MultiResolver_1.MultiResolver([ new VirtualResolver_1.VirtualResolver(), new WorkspaceResolver_1.WorkspaceResolver(), ...pluginResolvers, ])); } makeFetcher() { const pluginFetchers = []; for (const plugin of this.plugins.values()) for (const fetcher of plugin.fetchers || []) pluginFetchers.push(new fetcher()); return new MultiFetcher_1.MultiFetcher([ new VirtualFetcher_1.VirtualFetcher(), new WorkspaceFetcher_1.WorkspaceFetcher(), ...pluginFetchers, ]); } getLinkers() { const linkers = []; for (const plugin of this.plugins.values()) for (const linker of plugin.linkers || []) linkers.push(new linker()); return linkers; } getSupportedArchitectures() { const architecture = nodeUtils.getArchitecture(); const supportedArchitectures = this.get(`supportedArchitectures`); let os = supportedArchitectures.get(`os`); if (os !== null) os = os.map(value => value === `current` ? architecture.os : value); let cpu = supportedArchitectures.get(`cpu`); if (cpu !== null) cpu = cpu.map(value => value === `current` ? architecture.cpu : value); let libc = supportedArchitectures.get(`libc`); if (libc !== null) libc = miscUtils.mapAndFilter(libc, value => value === `current` ? architecture.libc ?? miscUtils.mapAndFilter.skip : value); return { os, cpu, libc }; } isInteractive({ interactive, stdout }) { if (!stdout.isTTY) return false; return interactive ?? this.get(`preferInteractive`); } /** * Computes and caches the package extensions. */ async getPackageExtensions() { if (this.packageExtensions !== null) return this.packageExtensions; this.packageExtensions = new Map(); const packageExtensions = this.packageExtensions; const registerPackageExtension = (descriptor, extensionData, { userProvided = false } = {}) => { if (!semverUtils.validRange(descriptor.range)) throw new Error(`Only semver ranges are allowed as keys for the packageExtensions setting`); const extension = new Manifest_1.Manifest(); extension.load(extensionData, { yamlCompatibilityMode: true }); const extensionsPerIdent = miscUtils.getArrayWithDefault(packageExtensions, descriptor.identHash); const extensionsPerRange = []; extensionsPerIdent.push([descriptor.range, extensionsPerRange]); const baseExtension = { status: types_1.PackageExtensionStatus.Inactive, userProvided, parentDescriptor: descriptor, }; for (const dependency of extension.dependencies.values()) extensionsPerRange.push({ ...baseExtension, type: types_1.PackageExtensionType.Dependency, descriptor: dependency }); for (const peerDependency of extension.peerDependencies.values()) extensionsPerRange.push({ ...baseExtension, type: types_1.PackageExtensionType.PeerDependency, descriptor: peerDependency }); for (const [selector, meta] of extension.peerDependenciesMeta) { for (const [key, value] of Object.entries(meta)) { extensionsPerRange.push({ ...baseExtension, type: types_1.PackageExtensionType.PeerDependencyMeta, selector, key: key, value }); } } }; await this.triggerHook(hooks => { return hooks.registerPackageExtensions; }, this, registerPackageExtension); for (const [descriptorString, extensionData] of this.get(`packageExtensions`)) registerPackageExtension(structUtils.parseDescriptor(descriptorString, true), miscUtils.convertMapsToIndexableObjects(extensionData), { userProvided: true }); return packageExtensions; } normalizeLocator(locator) { if (semverUtils.validRange(locator.reference)) return structUtils.makeLocator(locator, `${this.get(`defaultProtocol`)}${locator.reference}`); if (exports.TAG_REGEXP.test(locator.reference)) return structUtils.makeLocator(locator, `${this.get(`defaultProtocol`)}${locator.reference}`); return locator; } // TODO: Rename into `normalizeLocator`? // TODO: Move into `structUtils`, and remove references to `defaultProtocol` (we can make it a constant, same as the lockfile name) normalizeDependency(dependency) { if (semverUtils.validRange(dependency.range)) return structUtils.makeDescriptor(dependency, `${this.get(`defaultProtocol`)}${dependency.range}`); if (exports.TAG_REGEXP.test(dependency.range)) return structUtils.makeDescriptor(dependency, `${this.get(`defaultProtocol`)}${dependency.range}`); return dependency; } normalizeDependencyMap(dependencyMap) { return new Map([...dependencyMap].map(([key, dependency]) => { return [key, this.normalizeDependency(dependency)]; })); } normalizePackage(original, { packageExtensions }) { const pkg = structUtils.copyPackage(original); // We use the extensions to define additional dependencies that weren't // properly listed in the original package definition const extensionsPerIdent = packageExtensions.get(original.identHash); if (typeof extensionsPerIdent !== `undefined`) { const version = original.version; if (version !== null) { for (const [range, extensionsPerRange] of extensionsPerIdent) { if (!semverUtils.satisfiesWithPrereleases(version, range)) continue; for (const extension of extensionsPerRange) { // If an extension is active for a package but redundant // for another one, it should be considered active if (extension.status === types_1.PackageExtensionStatus.Inactive) extension.status = types_1.PackageExtensionStatus.Redundant; switch (extension.type) { case types_1.PackageExtensionType.Dependency: { const currentDependency = pkg.dependencies.get(extension.descriptor.identHash); if (typeof currentDependency === `undefined`) { extension.status = types_1.PackageExtensionStatus.Active; pkg.dependencies.set(extension.descriptor.identHash, this.normalizeDependency(extension.descriptor)); } } break; case types_1.PackageExtensionType.PeerDependency: { const currentPeerDependency = pkg.peerDependencies.get(extension.descriptor.identHash); if (typeof currentPeerDependency === `undefined`) { extension.status = types_1.PackageExtensionStatus.Active; pkg.peerDependencies.set(extension.descriptor.identHash, extension.descriptor); } } break; case types_1.PackageExtensionType.PeerDependencyMeta: { const currentPeerDependencyMeta = pkg.peerDependenciesMeta.get(extension.selector); if (typeof currentPeerDependencyMeta === `undefined` || !Object.hasOwn(currentPeerDependencyMeta, extension.key) || currentPeerDependencyMeta[extension.key] !== extension.value) { extension.status = types_1.PackageExtensionStatus.Active; miscUtils.getFactoryWithDefault(pkg.peerDependenciesMeta, extension.selector, () => ({}))[extension.key] = extension.value; } } break; default: { miscUtils.assertNever(extension); } } } } } } // We also add implicit optional @types peer dependencies for each peer // dependency. This is for compatibility reason, as many existing packages // forget to define their @types/react optional peer dependency when they // peer-depend on react. const getTypesName = (descriptor) => { return descriptor.scope ? `${descriptor.scope}__${descriptor.name}` : `${descriptor.name}`; }; // I don't like implicit dependencies, but package authors are reluctant to // use optional peer dependencies because they would print warnings in older // npm releases. for (const identString of pkg.peerDependenciesMeta.keys()) { const ident = structUtils.parseIdent(identString); if (!pkg.peerDependencies.has(ident.identHash)) { pkg.peerDependencies.set(ident.identHash, structUtils.makeDescriptor(ident, `*`)); } } // Automatically add corresponding `@types` optional peer dependencies for (const descriptor of pkg.peerDependencies.values()) { if (descriptor.scope === `types`) continue; const typesName = getTypesName(descriptor); const typesIdent = structUtils.makeIdent(`types`, typesName); const stringifiedTypesIdent = structUtils.stringifyIdent(typesIdent); if (pkg.peerDependencies.has(typesIdent.identHash) || pkg.peerDependenciesMeta.has(stringifiedTypesIdent)) continue; pkg.peerDependencies.set(typesIdent.identHash, structUtils.makeDescriptor(typesIdent, `*`)); pkg.peerDependenciesMeta.set(stringifiedTypesIdent, { optional: true, }); } // We sort the dependencies so that further iterations always occur in the // same order, regardless how the various registries formatted their output pkg.dependencies = new Map(miscUtils.sortMap(pkg.dependencies, ([, descriptor]) => structUtils.stringifyDescriptor(descriptor))); pkg.peerDependencies = new Map(miscUtils.sortMap(pkg.peerDependencies, ([, descriptor]) => structUtils.stringifyDescriptor(descriptor))); return pkg; } getLimit(key) { return miscUtils.getFactoryWithDefault(this.limits, key, () => { return (0, p_limit_1.default)(this.get(key)); }); } async triggerHook(get, ...args) { for (const plugin of this.plugins.values()) { const hooks = plugin.hooks; if (!hooks) continue; const hook = get(hooks); if (!hook) continue; await hook(...args); } } async triggerMultipleHooks(get, argsList) { for (const args of argsList) { await this.triggerHook(get, ...args); } } async reduceHook(get, initialValue, ...args) { let value = initialValue; for (const plugin of this.plugins.values()) { const hooks = plugin.hooks; if (!hooks) continue; const hook = get(hooks); if (!hook) continue; value = await hook(value, ...args); } return value; } async firstHook(get, ...args) { for (const plugin of this.plugins.values()) { const hooks = plugin.hooks; if (!hooks) continue; const hook = get(hooks); if (!hook) continue; const ret = await hook(...args); if (typeof ret !== `undefined`) { // @ts-expect-error - reason TBS return ret; } } return null; } } exports.Configuration = Configuration; Configuration.deleteProperty = Symbol(); Configuration.telemetry = null;