nx
Version:
613 lines (612 loc) • 27.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getYarnBerrySpawnRegistryEnv = getYarnBerrySpawnRegistryEnv;
const fs_1 = require("fs");
const minimatch_1 = require("minimatch");
const os_1 = require("os");
const path_1 = require("path");
const semver_1 = require("semver");
const fileutils_1 = require("../fileutils");
const logger_1 = require("../logger");
const npmrc_1 = require("../package-manager-config/npmrc");
const utils_1 = require("./utils");
/*
* yarn berry (2/3/4) registry resolution (verified on 2.4.2, 3.8.7, 4.16.0):
*
* - Sources, highest first: YARN_* env vars > project .yarnrc.yml > ancestor
* .yarnrc.yml files (closer dir wins) > ~/.yarnrc.yml > defaults
* (first-writer-wins for scalars; npmScopes/npmRegistries merge per entry).
* See https://github.com/yarnpkg/berry/blob/a26895a80d2784a5be92c54d5e7622bc9b0864a5/packages/yarnpkg-core/sources/Configuration.ts#L1424
* - Fetch registry for @scope/pkg: npmScopes.<scope-without-@>.npmRegistryServer,
* else npmRegistryServer (default https://registry.yarnpkg.com).
* - .npmrc files are completely ignored.
*
* Because npm-visible surfaces are irrelevant to berry, the resolved values
* are always injected (default and, for scoped targets, the scoped key), so a
* stray .npmrc cannot steer the spawned npm away from what berry would do.
*
* Auth (npmAuthToken/npmAuthIdent at scope > npmRegistries[registry] > global)
* lives in .yarnrc.yml, which npm cannot read; it is translated to nerf-darted
* npm keys. TLS: caFilePath (v2/3) / httpsCaFilePath (v4), the
* httpsCertFilePath/httpsKeyFilePath client-certificate pair (3.2.0 on),
* per-host networkSettings (hostname globs, longest key first), enableStrictSsl
* (global only). A matching networkSettings entry is read before the global
* value of the same key, so for those keys alone it outranks the env vars too.
* enableNetwork is resolved the same way but has no npm counterpart, so a host
* berry refuses to reach is reported rather than reproduced.
*/
const BERRY_DEFAULT_REGISTRY = 'https://registry.yarnpkg.com';
// Berry rewrote its env-var expander in 4.13.0, tightening the variable-name
// class from [\d\w_]+ (a leading _ or digit is valid) to [a-zA-Z]\w*.
const BERRY_ENV_PARSER_REWRITE = '4.13.0';
// httpsCertFilePath/httpsKeyFilePath landed in 3.2.0, as an rc setting and
// inside networkSettings at once. Below it berry aborts on the setting, so
// there is no resolution left to reproduce.
const BERRY_CLIENT_CERT_SETTINGS = '3.2.0';
const JSR_REGISTRY = 'https://npm.jsr.io';
function getYarnBerrySpawnRegistryEnv(packageName, root, yarnVersion) {
const env = {};
const scope = (0, utils_1.getPackageScope)(packageName);
const rcFiles = collectRcFiles(root);
// berry v4 deep-merges map settings (npmScopes/npmRegistries/networkSettings)
// per sub-key across rc files; v2/v3 take the whole entry from the highest-
// priority file that defines the key, omitted sub-keys included as their
// defaults.
const deepMerge = (0, semver_1.major)(yarnVersion) >= 4;
const resolveMapEntry = (pick) => {
if (!deepMerge) {
return firstDefinedIn(rcFiles, pick);
}
const merged = {};
let any = false;
for (const key of [
'npmRegistryServer',
'npmAuthToken',
'npmAuthIdent',
'npmAlwaysAuth',
]) {
const value = firstDefinedIn(rcFiles, (c) => pick(c)?.[key]);
if (value !== undefined) {
merged[key] = value;
any = true;
}
}
return any ? merged : undefined;
};
// Berry expands env vars in every string setting at parse time, so expand
// before the URL parse and nerf-dart. An unexpanded
// `npmRegistryServer: "${MY_REGISTRY}"` has no parseable host, so its auth
// and TLS keys are never emitted.
const legacy = (0, semver_1.lt)(yarnVersion, BERRY_ENV_PARSER_REWRITE);
const defaultRegistry = expandBerryEnvVars(process.env['YARN_NPM_REGISTRY_SERVER'] ??
firstDefinedIn(rcFiles, (c) => c.npmRegistryServer) ??
BERRY_DEFAULT_REGISTRY, legacy);
// npmScopes keys are scope names without the leading @.
const scopeName = scope?.slice(1);
const scopeEntry = scopeName
? resolveMapEntry((c) => c.npmScopes?.[scopeName])
: undefined;
// A scope configured in npmScopes (even auth-only or empty) with no
// npmRegistryServer routes to berry's shape default (registry.yarnpkg.com),
// not the top-level npmRegistryServer. Berry seeds the scope entry with that
// default and returns it directly.
const scopeConfigured = scopeName !== undefined &&
rcFiles.some((f) => f.config.npmScopes !== undefined &&
// Own keys only: the parsed-YAML object carries Object.prototype, and a
// scope named after one of its members (@constructor) is not configured.
Object.hasOwn(f.config.npmScopes, scopeName));
const jsrDefault = scopeName === 'jsr' && !scopeConfigured && (0, semver_1.gte)(yarnVersion, '4.9.0');
const effectiveRegistry = scopeConfigured
? expandBerryEnvVars(scopeEntry?.npmRegistryServer ?? BERRY_DEFAULT_REGISTRY, legacy)
: jsrDefault
? JSR_REGISTRY
: defaultRegistry;
(0, utils_1.setRegistry)(env, defaultRegistry);
if (scope) {
(0, utils_1.setScopedRegistry)(env, scope, effectiveRegistry);
}
// berry's getAuthConfiguration picks ONE config object by specificity and
// reads token-then-ident from that object alone, so the tiers never mix. A
// present-but-credential-less npmRegistries entry therefore emits no auth
// rather than falling back to the global or env credentials.
const registryKey = selectNpmRegistriesKey(rcFiles, effectiveRegistry);
let authToken;
let authIdent;
let alwaysAuth = false;
if (scopeEntry?.npmAuthToken !== undefined ||
scopeEntry?.npmAuthIdent !== undefined) {
authToken = scopeEntry.npmAuthToken;
authIdent = scopeEntry.npmAuthIdent;
alwaysAuth = isBerryTrueBoolean(scopeEntry.npmAlwaysAuth);
}
else if (registryKey !== undefined) {
const registryEntry = resolveMapEntry((c) => c.npmRegistries?.[registryKey]);
authToken = registryEntry?.npmAuthToken;
authIdent = registryEntry?.npmAuthIdent;
alwaysAuth = isBerryTrueBoolean(registryEntry?.npmAlwaysAuth);
}
else {
// berry's getAuthConfiguration ends on the merged configuration with no
// source-tier tracking, so a global npmAuthToken/npmAuthIdent (any file
// tier, the home rc included, or env) authenticates whichever registry the
// other tiers selected. Reproduced deliberately for parity.
authToken =
process.env['YARN_NPM_AUTH_TOKEN'] ??
firstDefinedIn(rcFiles, (c) => c.npmAuthToken);
authIdent =
process.env['YARN_NPM_AUTH_IDENT'] ??
firstDefinedIn(rcFiles, (c) => c.npmAuthIdent);
alwaysAuth = isBerryTrueBoolean(process.env['YARN_NPM_ALWAYS_AUTH'] ??
firstDefinedIn(rcFiles, (c) => c.npmAlwaysAuth));
}
// Expand ${VAR} before use so the npmAuthIdent base64 decision and the
// bridged value are computed on the real credentials, matching berry.
// Env-sourced values are literal (no-op).
authToken = expandBerryValue(authToken, legacy);
authIdent = expandBerryValue(authIdent, legacy);
// A scoped fetch authenticates (berry forces BEST_EFFORT); an unscoped fetch
// only when npmAlwaysAuth is set on the selected config (npm view/pack leaves
// berry's authType at CONFIGURATION).
if (scope || alwaysAuth) {
if (authToken) {
(0, utils_1.setAuthToken)(env, effectiveRegistry, authToken);
}
else if (authIdent) {
(0, utils_1.setAuthIdent)(env, effectiveRegistry, encodeIdent(authIdent, yarnVersion));
}
}
applyTls(env, rcFiles, effectiveRegistry, yarnVersion);
// Unlike yarn classic there is no gate to consult here. Everything berry
// would send is already in the overlay, which the warning checks against, so
// it runs once the last of it (the client certificate) is in. The dart is the
// one npm resolves for a request to this registry, since the overlay writes
// there and the check climbs from it.
const dart = (0, utils_1.requestNerfDart)(effectiveRegistry);
if (dart) {
(0, utils_1.warnNativeCredential)(env, dart, 'yarn', 'Remove that credential from .npmrc if npm should not authenticate there; yarn never reads that file.', npmrcReader(root));
}
// Berry's expander consumes the backslash that escapes a reference, so what
// it resolves can hold a `${VAR}` npm would go on to resolve for itself,
// sending a credential berry keeps literal. Escaping every value it produced
// hands npm the same text back. Keys are left as they are: a reference can
// only reach one through a registry host, which is not a host berry resolves
// either.
for (const [key, value] of Object.entries(env)) {
env[key] = (0, utils_1.escapeNpmEnvExpr)(value);
}
return env;
}
/**
* Reads a key the way the spawned npm would from the .npmrc files berry ignores.
* The env tier is left out on purpose: berry never reads npm_config_*, so the
* spawn strips every bridged ambient one (mergeNpmConfigEnv), the nerf-darted
* keys this reader is probed with are all bridged, and what berry does send
* is in the overlay the warning checks separately. npm also reads a
* <globalPrefix>/etc npmrc and its own builtin one, which are not enumerated
* here: missing one only means the warning stays silent. The maps are read once
* because the caller probes dozens of keys walking npm's credential ladder.
*/
function npmrcReader(root) {
const maps = [(0, path_1.join)(root, '.npmrc'), (0, path_1.join)((0, os_1.homedir)(), '.npmrc')].map((path) => {
const map = (0, npmrc_1.readNpmrcMap)(path);
// npm silently treats an .npmrc it cannot read as absent; this reader only
// mirrors npm's own view, so an unreadable file keeps the warning silent.
return map === 'unreadable' ? null : map;
});
return (key) => {
for (const map of maps) {
const value = map && (0, utils_1.readExpandedKey)(map, key, utils_1.expandNpmEnvVars);
if (value !== undefined) {
return value;
}
}
return undefined;
};
}
function collectRcFiles(root) {
// YARN_RC_FILENAME renames the files berry finds by walking up, but not the
// home one: findFolderRcFile reads the constant instead of the setting.
const rcName = process.env['YARN_RC_FILENAME'] ?? '.yarnrc.yml';
// berry looks the project and ancestor files up before reading them but opens
// the home one outright, so a symlink loop is absent in the first group and
// aborts yarn in the last (measured on 4.10.3).
const paths = [
{ path: (0, path_1.join)(root, rcName), lookedUp: true },
...(0, utils_1.ancestorDirectories)(root).map((dir) => ({
path: (0, path_1.join)(dir, rcName),
lookedUp: true,
})),
{ path: (0, path_1.join)((0, os_1.homedir)(), '.yarnrc.yml'), lookedUp: false },
];
const files = [];
for (const { path, lookedUp } of paths) {
if (lookedUp && !(0, fs_1.existsSync)(path)) {
continue;
}
// An unreadable, corrupt or non-mapping rc file aborts yarn itself, so there
// is no resolution left to reproduce. Every shape propagates to the caller's
// fall-open; dropping the file instead would resolve to berry's default
// registry while still bridging the credentials the other files declare.
let config;
try {
// berry loads its rc files through @yarnpkg/parsers, which calls js-yaml
// with the failsafe schema and json: true. That combination is what makes
// `npmAuthToken: 12345` a string and a repeated key last-wins rather than
// an error, so read them the same way instead of re-typing the scalars.
config = (0, fileutils_1.readYamlFile)(path, { json: true, failsafe: true });
}
catch (e) {
// Nothing looked this one up, so an absent file surfaces as the read's
// own ENOENT rather than as a missed lookup.
if (!lookedUp && e?.code === 'ENOENT') {
continue;
}
// The parse error quotes the lines around the fault, which in an rc file
// is credential material, and the caller logs whatever reaches it.
throw new Error(`The yarn rc file at ${path} could not be read.`);
}
// An empty or comment-only file is a valid rc that declares nothing.
if (config === undefined || config === null) {
continue;
}
if (typeof config !== 'object' || Array.isArray(config)) {
throw new Error(`The yarn rc file at ${path} is not a settings mapping.`);
}
normalizeMapSettings(config, path);
files.push({ path, config });
}
return files;
}
/**
* Berry types npmScopes/npmRegistries/networkSettings and their entries, and
* rejects the wrong shape before it resolves anything: a null is tolerated (that
* level keeps its defaults) but any other non-object aborts yarn with "must be
* an object". Reproduce both, so a config berry refuses to run on never reads as
* if berry had accepted it. Verified on 3.8.7 and 4.15.0.
*/
function normalizeMapSettings(config, path) {
config.npmScopes = normalizeMapSetting(config.npmScopes, 'npmScopes', path);
// npmRegistries is declared with normalizeKeys, so berry strips the trailing
// slash off each key as it loads the map, and lets a colliding key win last
// the way berry's Map does.
const registries = normalizeMapSetting(config.npmRegistries, 'npmRegistries', path);
config.npmRegistries = registries
? Object.fromEntries(Object.entries(registries).map(([key, entry]) => [
normalizeRegistryKey(key),
entry,
]))
: undefined;
config.networkSettings = normalizeMapSetting(config.networkSettings, 'networkSettings', path);
}
function normalizeMapSetting(map, setting, path) {
const entries = asBerryObject(map, setting, path);
if (!entries) {
return undefined;
}
for (const key of Object.keys(entries)) {
// A null entry is still a configured key, with every sub-setting defaulted.
entries[key] =
asBerryObject(entries[key], `${setting}["${key}"]`, path) ?? {};
}
return entries;
}
function asBerryObject(value, setting, path) {
if (value === undefined || value === null) {
return undefined;
}
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`The yarn setting "${setting}" in ${path} is not an object.`);
}
return value;
}
/**
* Berry looks the effective registry up in its merged npmRegistries map by exact
* key first and only then with the scheme stripped, so the key has to be picked
* across every rc file before any entry is read: an exact key in the home rc
* still beats a scheme-less one in the project rc.
*/
function selectNpmRegistriesKey(rcFiles, registry) {
const exact = normalizeRegistryKey(registry);
// berry strips any scheme (not just http/https) for the second lookup.
for (const key of [exact, exact.replace(/^[a-z]+:/, '')]) {
if (rcFiles.some((f) => f.config.npmRegistries?.[key] !== undefined)) {
return key;
}
}
return undefined;
}
/** Berry's normalizeRegistry, which it also applies to every npmRegistries key. */
function normalizeRegistryKey(registry) {
return registry.replace(/\/$/, '');
}
/**
* npm's `_auth` is base64(user:pass). Berry v2 stores npmAuthIdent already
* base64-encoded; v3/v4 accept both and base64-encode at request time only
* when the value still contains a `:`.
*/
function encodeIdent(ident, yarnVersion) {
if ((0, semver_1.major)(yarnVersion) >= 3 && ident.includes(':')) {
return Buffer.from(ident).toString('base64');
}
return ident;
}
function applyTls(env, rcFiles, effectiveRegistry, yarnVersion) {
const legacy = (0, semver_1.lt)(yarnVersion, BERRY_ENV_PARSER_REWRITE);
const v4 = (0, semver_1.major)(yarnVersion) >= 4;
// The other major's CA-setting name aborts berry, as an rc setting and as a
// YARN_* env var alike, so only the name the running major accepts has
// anything left to reproduce.
const caKey = v4 ? 'httpsCaFilePath' : 'caFilePath';
const caEnvKey = v4 ? 'YARN_HTTPS_CA_FILE_PATH' : 'YARN_CA_FILE_PATH';
let host;
try {
host = new URL(effectiveRegistry).hostname;
}
catch { }
// Berry's getNetworkSettings falls back to a global setting only after the
// matching per-host entries, so a per-host value outranks the YARN_* env vars
// while those still outrank the rc files.
const network = resolveNetworkSettings(rcFiles, host, v4);
// Berry expands env vars in path settings, then resolves an rc-sourced path
// relative to that rc file's directory and an env-sourced one relative to the
// directory yarn was invoked from.
const resolvePath = (key, envKey) => {
const path = network[key] ?? envPath(process.env[envKey]) ?? firstPathIn(rcFiles, key);
return path
? (0, path_1.resolve)(path.baseDir, expandBerryEnvVars(path.value, legacy))
: undefined;
};
const cafile = resolvePath(caKey, caEnvKey);
if (cafile) {
(0, utils_1.setCafile)(env, cafile);
}
if ((0, semver_1.gte)(yarnVersion, BERRY_CLIENT_CERT_SETTINGS)) {
// Berry sets cert and key independently (httpUtils has no joint gate); a
// lone half only fails there because Node cannot present a certificate
// without its key, so it stays out of the overlay.
const certfile = resolvePath('httpsCertFilePath', 'YARN_HTTPS_CERT_FILE_PATH');
const keyfile = resolvePath('httpsKeyFilePath', 'YARN_HTTPS_KEY_FILE_PATH');
if (certfile && keyfile) {
(0, utils_1.setClientCertificate)(env, effectiveRegistry, certfile, keyfile);
}
}
const enableNetwork = network.enableNetwork ??
process.env['YARN_ENABLE_NETWORK'] ??
firstDefinedIn(rcFiles, (c) => c.enableNetwork);
if (host &&
enableNetwork !== undefined &&
isBerryFalseBoolean(enableNetwork)) {
warnDisabledNetwork(host);
}
const strictSsl = process.env['YARN_ENABLE_STRICT_SSL'] ??
firstDefinedIn(rcFiles, (c) => c.enableStrictSsl);
if (strictSsl !== undefined) {
(0, utils_1.setStrictSsl)(env, !isBerryFalseBoolean(strictSsl));
}
(0, utils_1.setProxies)(env, {
httpProxy: expandBerryValue(network.httpProxy ??
process.env['YARN_HTTP_PROXY'] ??
firstDefinedIn(rcFiles, (c) => c.httpProxy), legacy),
httpsProxy: expandBerryValue(network.httpsProxy ??
process.env['YARN_HTTPS_PROXY'] ??
firstDefinedIn(rcFiles, (c) => c.httpsProxy), legacy),
});
}
let warnedDisabledNetwork = false;
/**
* Berry refuses to reach a host it has enableNetwork off for and exits (verified
* on 4.15.0: the registry is never contacted). The spawned npm has no such
* setting and will make the request, so say so once rather than reproducing a
* refusal that would leave the workspace un-migrated.
*/
function warnDisabledNetwork(host) {
if (warnedDisabledNetwork) {
return;
}
warnedDisabledNetwork = true;
logger_1.logger.warn(`yarn is configured not to use the network for ${host} (enableNetwork is false), so yarn itself would refuse to fetch from it. Packages will still be fetched from that registry.`);
}
function envPath(value) {
return value ? { value, baseDir: process.cwd() } : undefined;
}
function firstPathIn(rcFiles, key) {
for (const file of rcFiles) {
const value = file.config[key];
if (value) {
return { value, baseDir: (0, path_1.dirname)(file.path) };
}
}
return undefined;
}
const BERRY_PATH_KEYS = [
'caFilePath',
'httpsCaFilePath',
'httpsCertFilePath',
'httpsKeyFilePath',
];
// Reproduces berry getNetworkSettings. Berry builds the merged map lowest
// priority first and its sort is stable, so two globs of equal length are
// consulted in that same order.
function resolveNetworkSettings(rcFiles, host, deepMerge) {
const result = {};
if (!host) {
return result;
}
const merged = new Map();
for (const file of [...rcFiles].reverse()) {
const settings = file.config.networkSettings;
if (!settings) {
continue;
}
const baseDir = (0, path_1.dirname)(file.path);
for (const [hostKey, entry] of Object.entries(settings)) {
// Walking lowest priority first, a later file is the higher-priority one:
// under v4 each sub-key it defines wins, and under v2/v3 it owns the whole
// entry, so a lower file never contributes a missing sub-key there.
const m = (deepMerge && merged.get(hostKey)) || {};
for (const key of BERRY_PATH_KEYS) {
if (entry[key] != null) {
m[key] = { value: entry[key], baseDir };
}
}
if (entry.enableNetwork != null) {
m.enableNetwork = entry.enableNetwork;
}
if (entry.httpProxy != null) {
m.httpProxy = entry.httpProxy;
}
if (entry.httpsProxy != null) {
m.httpsProxy = entry.httpsProxy;
}
merged.set(hostKey, m);
}
}
const matching = [...merged.entries()]
.filter(([key]) => matchesHostGlob(host, key))
.sort((a, b) => b[0].length - a[0].length)
.map(([, m]) => m);
for (const m of matching) {
for (const key of BERRY_PATH_KEYS) {
result[key] ??= m[key];
}
result.enableNetwork ??= m.enableNetwork;
result.httpProxy ??= m.httpProxy;
result.httpsProxy ??= m.httpsProxy;
}
return result;
}
/**
* Berry matches a networkSettings key against the hostname with micromatch,
* which parses two forms differently from minimatch: a bare `(a|b)` is an
* extglob alternation rather than literal parentheses, and a leading `!` negates
* the pattern only when it is not the start of a `!(...)` extglob. Left to
* minimatch a `!(...)` key matches nearly every host, which would hand one
* host's CA and proxy to the registry.
*/
function matchesHostGlob(host, key) {
return (0, minimatch_1.minimatch)(host, key.replace(/(^|[^\\@!+*?])\(/g, '$1@('), {
nonegate: key.startsWith('!('),
});
}
// Berry's miscUtils.replaceEnvVariables, applied to every string setting:
// ${VAR}, ${VAR-default} (default when unset) and ${VAR:-default} (default when
// unset or empty). Berry throws on an undefined bare ${VAR}, which aborts berry
// itself, so a working workspace never has one; we leave the reference literal
// rather than failing the migrate.
// see https://github.com/yarnpkg/berry/blob/c5857bdee5737425b879492db5e2732a5e6e14f2/packages/yarnpkg-core/sources/miscUtils.ts#L473
function expandBerryEnvVars(value, legacy, env = process.env) {
return legacy
? expandBerryEnvVarsLegacy(value, env)
: scanBerryEnv(value, 0, env, false).text;
}
// Berry's pre-4.13 miscUtils.replaceEnvVariables.
// See https://github.com/yarnpkg/berry/blob/%40yarnpkg/cli/4.12.0/packages/yarnpkg-core/sources/miscUtils.ts
function expandBerryEnvVarsLegacy(value, env) {
return value.replace(/\\?\$\{(?<variableName>[\d\w_]+)(?<colon>:)?(?:-(?<fallback>[^}]*))?\}/g, (match, ...args) => {
if (match.startsWith('\\')) {
return match.slice(1);
}
const { variableName, colon, fallback } = args[args.length - 1];
const resolved = env[variableName];
if (resolved || (Object.hasOwn(env, variableName) && !colon)) {
return resolved;
}
return fallback ?? match;
});
}
// Scans from `start`, expanding env-var references. When `nested` is set the
// scan also stops just after the unescaped `}` that closes an enclosing default.
function scanBerryEnv(input, start, env, nested) {
let text = '';
let i = start;
while (i < input.length) {
const c = input[i];
if (c === '\\' && i + 1 < input.length && '\\$}'.includes(input[i + 1])) {
text += input[i + 1];
i += 2;
}
else if (nested && c === '}') {
return { text, end: i + 1 };
}
else if (c === '$' && input[i + 1] === '{') {
const ref = parseBerryRef(input, i, env);
if (ref) {
text += ref.text;
i = ref.end;
}
else {
// Malformed `${...` (berry would throw); emit it literally and continue.
text += '${';
i += 2;
}
}
else {
text += c;
i += 1;
}
}
return { text, end: i };
}
// Parses one ${NAME} / ${NAME-default} / ${NAME:-default} at `start`, which
// must be the `${`. The default is scanned brace-balanced so a nested ${...} is
// captured whole.
function parseBerryRef(input, start, env) {
const nameMatch = input.slice(start + 2).match(/^[a-zA-Z]\w*/);
if (!nameMatch) {
return null;
}
const name = nameMatch[0];
let i = start + 2 + name.length;
const value = env[name];
if (input[i] === '}') {
// ${NAME}: an undefined value aborts berry; leave the reference literal.
return {
text: value !== undefined ? value : input.slice(start, i + 1),
end: i + 1,
};
}
let emptyIsUnset = false;
if (input[i] === ':' && input[i + 1] === '-') {
emptyIsUnset = true;
i += 2;
}
else if (input[i] === '-') {
i += 1;
}
else {
return null;
}
// Parse (and expand) the default region even when the value wins, so `end`
// skips past the matching close brace.
const fallback = scanBerryEnv(input, i, env, true);
const useValue = emptyIsUnset
? value !== undefined && value !== ''
: value !== undefined;
return {
text: useValue ? value : fallback.text,
end: fallback.end,
};
}
// Berry's miscUtils.parseBoolean false set for SettingsType.BOOLEAN.
function isBerryFalseBoolean(value) {
return value === false || value === 0 || value === 'false' || value === '0';
}
// Its true set. Berry throws on a value in neither, which aborts berry itself,
// so each side keeps the tolerance that is safe for its setting: an unparseable
// enableStrictSsl keeps TLS verification on, an unparseable npmAlwaysAuth leaves
// an unscoped fetch unauthenticated.
function isBerryTrueBoolean(value) {
return value === true || value === 1 || value === 'true' || value === '1';
}
function expandBerryValue(value, legacy) {
return value === undefined ? undefined : expandBerryEnvVars(value, legacy);
}
function firstDefinedIn(rcFiles, read) {
for (const file of rcFiles) {
const value = read(file.config);
if (value !== undefined) {
return value;
}
}
return undefined;
}