nx 23.1.1 → 23.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/release/changelog-renderer/index.js +4 -5
- package/dist/release/index.d.ts +1 -1
- package/dist/src/command-line/add/add.js +5 -0
- package/dist/src/command-line/affected/affected.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
- package/dist/src/command-line/import/import.js +1 -2
- package/dist/src/command-line/init/init-v2.js +4 -5
- package/dist/src/command-line/migrate/migrate.d.ts +4 -0
- package/dist/src/command-line/migrate/migrate.js +89 -26
- package/dist/src/command-line/release/changelog.js +5 -0
- package/dist/src/command-line/release/index.d.ts +1 -1
- package/dist/src/command-line/release/utils/git.js +1 -1
- package/dist/src/command-line/release/utils/release-graph.d.ts +17 -0
- package/dist/src/command-line/release/utils/release-graph.js +71 -19
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +6 -0
- package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
- package/dist/src/command-line/release/version/release-group-processor.js +80 -6
- package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
- package/dist/src/command-line/release/version/version-actions.js +9 -1
- package/dist/src/command-line/release/version.js +9 -5
- package/dist/src/command-line/reset/reset.js +6 -1
- package/dist/src/command-line/run/run-one.js +1 -0
- package/dist/src/command-line/run-many/run-many.js +1 -0
- package/dist/src/command-line/watch/watch.js +13 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/styles.js +1 -1
- package/dist/src/daemon/client/client.d.ts +52 -1
- package/dist/src/daemon/client/client.js +160 -36
- package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
- package/dist/src/daemon/client/daemon-environment.js +110 -2
- package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
- package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
- package/dist/src/daemon/message-types/daemon-message.js +25 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
- package/dist/src/daemon/server/latest-nx.js +5 -2
- package/dist/src/daemon/server/server.js +24 -0
- package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
- package/dist/src/daemon/server/shutdown-utils.js +11 -4
- package/dist/src/daemon/socket-utils.d.ts +1 -0
- package/dist/src/daemon/socket-utils.js +24 -3
- package/dist/src/daemon/tmp-dir.d.ts +43 -3
- package/dist/src/daemon/tmp-dir.js +437 -15
- package/dist/src/devkit-internals.d.ts +93 -3
- package/dist/src/devkit-internals.js +192 -5
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
- package/dist/src/generators/tree.d.ts +4 -2
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
- package/dist/src/native/index.d.ts +10 -0
- package/dist/src/native/index.js +71 -28
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/native-file-cache-location.d.ts +17 -1
- package/dist/src/native/native-file-cache-location.js +86 -12
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
- package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
- package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +60 -0
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
- package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
- package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
- package/dist/src/project-graph/error-types.d.ts +12 -1
- package/dist/src/project-graph/error-types.js +27 -2
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
- package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
- package/dist/src/project-graph/project-graph.js +20 -0
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +5 -1
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +133 -3
- package/dist/src/tasks-runner/task-env.js +5 -2
- package/dist/src/tasks-runner/utils.js +2 -6
- package/dist/src/utils/assert-workspace-validity.js +7 -2
- package/dist/src/utils/fileutils.d.ts +5 -0
- package/dist/src/utils/fileutils.js +7 -2
- package/dist/src/utils/git-worktrees.d.ts +30 -0
- package/dist/src/utils/git-worktrees.js +212 -0
- package/dist/src/utils/ignore.d.ts +128 -1
- package/dist/src/utils/ignore.js +182 -12
- package/dist/src/utils/long-running-target.d.ts +10 -0
- package/dist/src/utils/long-running-target.js +19 -0
- package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
- package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
- package/dist/src/utils/nx-tmp-dir.js +40 -0
- package/dist/src/utils/owned-private-dir.d.ts +132 -0
- package/dist/src/utils/owned-private-dir.js +323 -0
- package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
- package/dist/src/utils/package-manager-config/bunfig.js +49 -0
- package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
- package/dist/src/utils/package-manager-config/npmrc.js +124 -0
- package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
- package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
- package/dist/src/utils/package-manager.d.ts +21 -2
- package/dist/src/utils/package-manager.js +244 -47
- package/dist/src/utils/provenance.js +6 -14
- package/dist/src/utils/registry-config/bun.d.ts +2 -0
- package/dist/src/utils/registry-config/bun.js +257 -0
- package/dist/src/utils/registry-config/index.d.ts +13 -0
- package/dist/src/utils/registry-config/index.js +102 -0
- package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
- package/dist/src/utils/registry-config/pnpm.js +1551 -0
- package/dist/src/utils/registry-config/utils.d.ts +195 -0
- package/dist/src/utils/registry-config/utils.js +496 -0
- package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-berry.js +612 -0
- package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
- package/dist/src/utils/safe-spawn.d.ts +24 -0
- package/dist/src/utils/safe-spawn.js +104 -0
- package/dist/src/utils/shell-quoting.d.ts +15 -0
- package/dist/src/utils/shell-quoting.js +35 -0
- package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
- package/dist/src/utils/wait-for-socket-connection.js +14 -8
- package/migrations.json +6 -0
- package/package.json +12 -12
- package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
- package/dist/src/utils/min-release-age/npmrc.js +0 -45
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getYarnBerrySpawnRegistryEnv = getYarnBerrySpawnRegistryEnv;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const minimatch_1 = require("minimatch");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const semver_1 = require("semver");
|
|
9
|
+
const fileutils_1 = require("../fileutils");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const npmrc_1 = require("../package-manager-config/npmrc");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
/*
|
|
14
|
+
* yarn berry (2/3/4) registry resolution (verified on 2.4.2, 3.8.7, 4.16.0):
|
|
15
|
+
*
|
|
16
|
+
* - Sources, highest first: YARN_* env vars > project .yarnrc.yml > ancestor
|
|
17
|
+
* .yarnrc.yml files (closer dir wins) > ~/.yarnrc.yml > defaults
|
|
18
|
+
* (first-writer-wins for scalars; npmScopes/npmRegistries merge per entry).
|
|
19
|
+
* See https://github.com/yarnpkg/berry/blob/a26895a80d2784a5be92c54d5e7622bc9b0864a5/packages/yarnpkg-core/sources/Configuration.ts#L1424
|
|
20
|
+
* - Fetch registry for @scope/pkg: npmScopes.<scope-without-@>.npmRegistryServer,
|
|
21
|
+
* else npmRegistryServer (default https://registry.yarnpkg.com).
|
|
22
|
+
* - .npmrc files are completely ignored.
|
|
23
|
+
*
|
|
24
|
+
* Because npm-visible surfaces are irrelevant to berry, the resolved values
|
|
25
|
+
* are always injected (default and, for scoped targets, the scoped key), so a
|
|
26
|
+
* stray .npmrc cannot steer the spawned npm away from what berry would do.
|
|
27
|
+
*
|
|
28
|
+
* Auth (npmAuthToken/npmAuthIdent at scope > npmRegistries[registry] > global)
|
|
29
|
+
* lives in .yarnrc.yml, which npm cannot read; it is translated to nerf-darted
|
|
30
|
+
* npm keys. TLS: caFilePath (v2/3) / httpsCaFilePath (v4), the
|
|
31
|
+
* httpsCertFilePath/httpsKeyFilePath client-certificate pair (3.2.0 on),
|
|
32
|
+
* per-host networkSettings (hostname globs, longest key first), enableStrictSsl
|
|
33
|
+
* (global only). A matching networkSettings entry is read before the global
|
|
34
|
+
* value of the same key, so for those keys alone it outranks the env vars too.
|
|
35
|
+
* enableNetwork is resolved the same way but has no npm counterpart, so a host
|
|
36
|
+
* berry refuses to reach is reported rather than reproduced.
|
|
37
|
+
*/
|
|
38
|
+
const BERRY_DEFAULT_REGISTRY = 'https://registry.yarnpkg.com';
|
|
39
|
+
// Berry rewrote its env-var expander in 4.13.0, tightening the variable-name
|
|
40
|
+
// class from [\d\w_]+ (a leading _ or digit is valid) to [a-zA-Z]\w*.
|
|
41
|
+
const BERRY_ENV_PARSER_REWRITE = '4.13.0';
|
|
42
|
+
// httpsCertFilePath/httpsKeyFilePath landed in 3.2.0, as an rc setting and
|
|
43
|
+
// inside networkSettings at once. Below it berry aborts on the setting, so
|
|
44
|
+
// there is no resolution left to reproduce.
|
|
45
|
+
const BERRY_CLIENT_CERT_SETTINGS = '3.2.0';
|
|
46
|
+
const JSR_REGISTRY = 'https://npm.jsr.io';
|
|
47
|
+
function getYarnBerrySpawnRegistryEnv(packageName, root, yarnVersion) {
|
|
48
|
+
const env = {};
|
|
49
|
+
const scope = (0, utils_1.getPackageScope)(packageName);
|
|
50
|
+
const rcFiles = collectRcFiles(root);
|
|
51
|
+
// berry v4 deep-merges map settings (npmScopes/npmRegistries/networkSettings)
|
|
52
|
+
// per sub-key across rc files; v2/v3 take the whole entry from the highest-
|
|
53
|
+
// priority file that defines the key, omitted sub-keys included as their
|
|
54
|
+
// defaults.
|
|
55
|
+
const deepMerge = (0, semver_1.major)(yarnVersion) >= 4;
|
|
56
|
+
const resolveMapEntry = (pick) => {
|
|
57
|
+
if (!deepMerge) {
|
|
58
|
+
return firstDefinedIn(rcFiles, pick);
|
|
59
|
+
}
|
|
60
|
+
const merged = {};
|
|
61
|
+
let any = false;
|
|
62
|
+
for (const key of [
|
|
63
|
+
'npmRegistryServer',
|
|
64
|
+
'npmAuthToken',
|
|
65
|
+
'npmAuthIdent',
|
|
66
|
+
'npmAlwaysAuth',
|
|
67
|
+
]) {
|
|
68
|
+
const value = firstDefinedIn(rcFiles, (c) => pick(c)?.[key]);
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
merged[key] = value;
|
|
71
|
+
any = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return any ? merged : undefined;
|
|
75
|
+
};
|
|
76
|
+
// Berry expands env vars in every string setting at parse time, so expand
|
|
77
|
+
// before the URL parse and nerf-dart. An unexpanded
|
|
78
|
+
// `npmRegistryServer: "${MY_REGISTRY}"` has no parseable host, so its auth
|
|
79
|
+
// and TLS keys are never emitted.
|
|
80
|
+
const legacy = (0, semver_1.lt)(yarnVersion, BERRY_ENV_PARSER_REWRITE);
|
|
81
|
+
const defaultRegistry = expandBerryEnvVars(process.env['YARN_NPM_REGISTRY_SERVER'] ??
|
|
82
|
+
firstDefinedIn(rcFiles, (c) => c.npmRegistryServer) ??
|
|
83
|
+
BERRY_DEFAULT_REGISTRY, legacy);
|
|
84
|
+
// npmScopes keys are scope names without the leading @.
|
|
85
|
+
const scopeName = scope?.slice(1);
|
|
86
|
+
const scopeEntry = scopeName
|
|
87
|
+
? resolveMapEntry((c) => c.npmScopes?.[scopeName])
|
|
88
|
+
: undefined;
|
|
89
|
+
// A scope configured in npmScopes (even auth-only or empty) with no
|
|
90
|
+
// npmRegistryServer routes to berry's shape default (registry.yarnpkg.com),
|
|
91
|
+
// not the top-level npmRegistryServer. Berry seeds the scope entry with that
|
|
92
|
+
// default and returns it directly.
|
|
93
|
+
const scopeConfigured = scopeName !== undefined &&
|
|
94
|
+
rcFiles.some((f) => f.config.npmScopes !== undefined &&
|
|
95
|
+
// Own keys only: the parsed-YAML object carries Object.prototype, and a
|
|
96
|
+
// scope named after one of its members (@constructor) is not configured.
|
|
97
|
+
Object.hasOwn(f.config.npmScopes, scopeName));
|
|
98
|
+
const jsrDefault = scopeName === 'jsr' && !scopeConfigured && (0, semver_1.gte)(yarnVersion, '4.9.0');
|
|
99
|
+
const effectiveRegistry = scopeConfigured
|
|
100
|
+
? expandBerryEnvVars(scopeEntry?.npmRegistryServer ?? BERRY_DEFAULT_REGISTRY, legacy)
|
|
101
|
+
: jsrDefault
|
|
102
|
+
? JSR_REGISTRY
|
|
103
|
+
: defaultRegistry;
|
|
104
|
+
(0, utils_1.setRegistry)(env, defaultRegistry);
|
|
105
|
+
if (scope) {
|
|
106
|
+
(0, utils_1.setScopedRegistry)(env, scope, effectiveRegistry);
|
|
107
|
+
}
|
|
108
|
+
// berry's getAuthConfiguration picks ONE config object by specificity and
|
|
109
|
+
// reads token-then-ident from that object alone, so the tiers never mix. A
|
|
110
|
+
// present-but-credential-less npmRegistries entry therefore emits no auth
|
|
111
|
+
// rather than falling back to the global or env credentials.
|
|
112
|
+
const registryKey = selectNpmRegistriesKey(rcFiles, effectiveRegistry);
|
|
113
|
+
let authToken;
|
|
114
|
+
let authIdent;
|
|
115
|
+
let alwaysAuth = false;
|
|
116
|
+
if (scopeEntry?.npmAuthToken !== undefined ||
|
|
117
|
+
scopeEntry?.npmAuthIdent !== undefined) {
|
|
118
|
+
authToken = scopeEntry.npmAuthToken;
|
|
119
|
+
authIdent = scopeEntry.npmAuthIdent;
|
|
120
|
+
alwaysAuth = isBerryTrueBoolean(scopeEntry.npmAlwaysAuth);
|
|
121
|
+
}
|
|
122
|
+
else if (registryKey !== undefined) {
|
|
123
|
+
const registryEntry = resolveMapEntry((c) => c.npmRegistries?.[registryKey]);
|
|
124
|
+
authToken = registryEntry?.npmAuthToken;
|
|
125
|
+
authIdent = registryEntry?.npmAuthIdent;
|
|
126
|
+
alwaysAuth = isBerryTrueBoolean(registryEntry?.npmAlwaysAuth);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// berry's getAuthConfiguration ends on the merged configuration with no
|
|
130
|
+
// source-tier tracking, so a global npmAuthToken/npmAuthIdent (any file
|
|
131
|
+
// tier, the home rc included, or env) authenticates whichever registry the
|
|
132
|
+
// other tiers selected. Reproduced deliberately for parity.
|
|
133
|
+
authToken =
|
|
134
|
+
process.env['YARN_NPM_AUTH_TOKEN'] ??
|
|
135
|
+
firstDefinedIn(rcFiles, (c) => c.npmAuthToken);
|
|
136
|
+
authIdent =
|
|
137
|
+
process.env['YARN_NPM_AUTH_IDENT'] ??
|
|
138
|
+
firstDefinedIn(rcFiles, (c) => c.npmAuthIdent);
|
|
139
|
+
alwaysAuth = isBerryTrueBoolean(process.env['YARN_NPM_ALWAYS_AUTH'] ??
|
|
140
|
+
firstDefinedIn(rcFiles, (c) => c.npmAlwaysAuth));
|
|
141
|
+
}
|
|
142
|
+
// Expand ${VAR} before use so the npmAuthIdent base64 decision and the
|
|
143
|
+
// bridged value are computed on the real credentials, matching berry.
|
|
144
|
+
// Env-sourced values are literal (no-op).
|
|
145
|
+
authToken = expandBerryValue(authToken, legacy);
|
|
146
|
+
authIdent = expandBerryValue(authIdent, legacy);
|
|
147
|
+
// A scoped fetch authenticates (berry forces BEST_EFFORT); an unscoped fetch
|
|
148
|
+
// only when npmAlwaysAuth is set on the selected config (npm view/pack leaves
|
|
149
|
+
// berry's authType at CONFIGURATION).
|
|
150
|
+
if (scope || alwaysAuth) {
|
|
151
|
+
if (authToken) {
|
|
152
|
+
(0, utils_1.setAuthToken)(env, effectiveRegistry, authToken);
|
|
153
|
+
}
|
|
154
|
+
else if (authIdent) {
|
|
155
|
+
(0, utils_1.setAuthIdent)(env, effectiveRegistry, encodeIdent(authIdent, yarnVersion));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
applyTls(env, rcFiles, effectiveRegistry, yarnVersion);
|
|
159
|
+
// Unlike yarn classic there is no gate to consult here. Everything berry
|
|
160
|
+
// would send is already in the overlay, which the warning checks against, so
|
|
161
|
+
// it runs once the last of it (the client certificate) is in. The dart is the
|
|
162
|
+
// one npm resolves for a request to this registry, since the overlay writes
|
|
163
|
+
// there and the check climbs from it.
|
|
164
|
+
const dart = (0, utils_1.requestNerfDart)(effectiveRegistry);
|
|
165
|
+
if (dart) {
|
|
166
|
+
(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));
|
|
167
|
+
}
|
|
168
|
+
// Berry's expander consumes the backslash that escapes a reference, so what
|
|
169
|
+
// it resolves can hold a `${VAR}` npm would go on to resolve for itself,
|
|
170
|
+
// sending a credential berry keeps literal. Escaping every value it produced
|
|
171
|
+
// hands npm the same text back. Keys are left as they are: a reference can
|
|
172
|
+
// only reach one through a registry host, which is not a host berry resolves
|
|
173
|
+
// either.
|
|
174
|
+
for (const [key, value] of Object.entries(env)) {
|
|
175
|
+
env[key] = (0, utils_1.escapeNpmEnvExpr)(value);
|
|
176
|
+
}
|
|
177
|
+
return env;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Reads a key the way the spawned npm would from the .npmrc files berry ignores.
|
|
181
|
+
* The env tier is left out on purpose: berry never reads npm_config_*, so the
|
|
182
|
+
* spawn strips every bridged ambient one (mergeNpmConfigEnv), the nerf-darted
|
|
183
|
+
* keys this reader is probed with are all bridged, and what berry does send
|
|
184
|
+
* is in the overlay the warning checks separately. npm also reads a
|
|
185
|
+
* <globalPrefix>/etc npmrc and its own builtin one, which are not enumerated
|
|
186
|
+
* here: missing one only means the warning stays silent. The maps are read once
|
|
187
|
+
* because the caller probes dozens of keys walking npm's credential ladder.
|
|
188
|
+
*/
|
|
189
|
+
function npmrcReader(root) {
|
|
190
|
+
const maps = [(0, path_1.join)(root, '.npmrc'), (0, path_1.join)((0, os_1.homedir)(), '.npmrc')].map((path) => {
|
|
191
|
+
const map = (0, npmrc_1.readNpmrcMap)(path);
|
|
192
|
+
// npm silently treats an .npmrc it cannot read as absent; this reader only
|
|
193
|
+
// mirrors npm's own view, so an unreadable file keeps the warning silent.
|
|
194
|
+
return map === 'unreadable' ? null : map;
|
|
195
|
+
});
|
|
196
|
+
return (key) => {
|
|
197
|
+
for (const map of maps) {
|
|
198
|
+
const value = map && (0, utils_1.readExpandedKey)(map, key, utils_1.expandNpmEnvVars);
|
|
199
|
+
if (value !== undefined) {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function collectRcFiles(root) {
|
|
207
|
+
// YARN_RC_FILENAME renames the files berry finds by walking up, but not the
|
|
208
|
+
// home one: findFolderRcFile reads the constant instead of the setting.
|
|
209
|
+
const rcName = process.env['YARN_RC_FILENAME'] ?? '.yarnrc.yml';
|
|
210
|
+
// berry looks the project and ancestor files up before reading them but opens
|
|
211
|
+
// the home one outright, so a symlink loop is absent in the first group and
|
|
212
|
+
// aborts yarn in the last (measured on 4.10.3).
|
|
213
|
+
const paths = [
|
|
214
|
+
{ path: (0, path_1.join)(root, rcName), lookedUp: true },
|
|
215
|
+
...(0, utils_1.ancestorDirectories)(root).map((dir) => ({
|
|
216
|
+
path: (0, path_1.join)(dir, rcName),
|
|
217
|
+
lookedUp: true,
|
|
218
|
+
})),
|
|
219
|
+
{ path: (0, path_1.join)((0, os_1.homedir)(), '.yarnrc.yml'), lookedUp: false },
|
|
220
|
+
];
|
|
221
|
+
const files = [];
|
|
222
|
+
for (const { path, lookedUp } of paths) {
|
|
223
|
+
if (lookedUp && !(0, fs_1.existsSync)(path)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// An unreadable, corrupt or non-mapping rc file aborts yarn itself, so there
|
|
227
|
+
// is no resolution left to reproduce. Every shape propagates to the caller's
|
|
228
|
+
// fall-open; dropping the file instead would resolve to berry's default
|
|
229
|
+
// registry while still bridging the credentials the other files declare.
|
|
230
|
+
let config;
|
|
231
|
+
try {
|
|
232
|
+
// berry loads its rc files through @yarnpkg/parsers, which calls js-yaml
|
|
233
|
+
// with the failsafe schema and json: true. That combination is what makes
|
|
234
|
+
// `npmAuthToken: 12345` a string and a repeated key last-wins rather than
|
|
235
|
+
// an error, so read them the same way instead of re-typing the scalars.
|
|
236
|
+
config = (0, fileutils_1.readYamlFile)(path, { json: true, failsafe: true });
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
// Nothing looked this one up, so an absent file surfaces as the read's
|
|
240
|
+
// own ENOENT rather than as a missed lookup.
|
|
241
|
+
if (!lookedUp && e?.code === 'ENOENT') {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// The parse error quotes the lines around the fault, which in an rc file
|
|
245
|
+
// is credential material, and the caller logs whatever reaches it.
|
|
246
|
+
throw new Error(`The yarn rc file at ${path} could not be read.`);
|
|
247
|
+
}
|
|
248
|
+
// An empty or comment-only file is a valid rc that declares nothing.
|
|
249
|
+
if (config === undefined || config === null) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (typeof config !== 'object' || Array.isArray(config)) {
|
|
253
|
+
throw new Error(`The yarn rc file at ${path} is not a settings mapping.`);
|
|
254
|
+
}
|
|
255
|
+
normalizeMapSettings(config, path);
|
|
256
|
+
files.push({ path, config });
|
|
257
|
+
}
|
|
258
|
+
return files;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Berry types npmScopes/npmRegistries/networkSettings and their entries, and
|
|
262
|
+
* rejects the wrong shape before it resolves anything: a null is tolerated (that
|
|
263
|
+
* level keeps its defaults) but any other non-object aborts yarn with "must be
|
|
264
|
+
* an object". Reproduce both, so a config berry refuses to run on never reads as
|
|
265
|
+
* if berry had accepted it. Verified on 3.8.7 and 4.15.0.
|
|
266
|
+
*/
|
|
267
|
+
function normalizeMapSettings(config, path) {
|
|
268
|
+
config.npmScopes = normalizeMapSetting(config.npmScopes, 'npmScopes', path);
|
|
269
|
+
// npmRegistries is declared with normalizeKeys, so berry strips the trailing
|
|
270
|
+
// slash off each key as it loads the map, and lets a colliding key win last
|
|
271
|
+
// the way berry's Map does.
|
|
272
|
+
const registries = normalizeMapSetting(config.npmRegistries, 'npmRegistries', path);
|
|
273
|
+
config.npmRegistries = registries
|
|
274
|
+
? Object.fromEntries(Object.entries(registries).map(([key, entry]) => [
|
|
275
|
+
normalizeRegistryKey(key),
|
|
276
|
+
entry,
|
|
277
|
+
]))
|
|
278
|
+
: undefined;
|
|
279
|
+
config.networkSettings = normalizeMapSetting(config.networkSettings, 'networkSettings', path);
|
|
280
|
+
}
|
|
281
|
+
function normalizeMapSetting(map, setting, path) {
|
|
282
|
+
const entries = asBerryObject(map, setting, path);
|
|
283
|
+
if (!entries) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
for (const key of Object.keys(entries)) {
|
|
287
|
+
// A null entry is still a configured key, with every sub-setting defaulted.
|
|
288
|
+
entries[key] =
|
|
289
|
+
asBerryObject(entries[key], `${setting}["${key}"]`, path) ?? {};
|
|
290
|
+
}
|
|
291
|
+
return entries;
|
|
292
|
+
}
|
|
293
|
+
function asBerryObject(value, setting, path) {
|
|
294
|
+
if (value === undefined || value === null) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
298
|
+
throw new Error(`The yarn setting "${setting}" in ${path} is not an object.`);
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Berry looks the effective registry up in its merged npmRegistries map by exact
|
|
304
|
+
* key first and only then with the scheme stripped, so the key has to be picked
|
|
305
|
+
* across every rc file before any entry is read: an exact key in the home rc
|
|
306
|
+
* still beats a scheme-less one in the project rc.
|
|
307
|
+
*/
|
|
308
|
+
function selectNpmRegistriesKey(rcFiles, registry) {
|
|
309
|
+
const exact = normalizeRegistryKey(registry);
|
|
310
|
+
// berry strips any scheme (not just http/https) for the second lookup.
|
|
311
|
+
for (const key of [exact, exact.replace(/^[a-z]+:/, '')]) {
|
|
312
|
+
if (rcFiles.some((f) => f.config.npmRegistries?.[key] !== undefined)) {
|
|
313
|
+
return key;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
/** Berry's normalizeRegistry, which it also applies to every npmRegistries key. */
|
|
319
|
+
function normalizeRegistryKey(registry) {
|
|
320
|
+
return registry.replace(/\/$/, '');
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* npm's `_auth` is base64(user:pass). Berry v2 stores npmAuthIdent already
|
|
324
|
+
* base64-encoded; v3/v4 accept both and base64-encode at request time only
|
|
325
|
+
* when the value still contains a `:`.
|
|
326
|
+
*/
|
|
327
|
+
function encodeIdent(ident, yarnVersion) {
|
|
328
|
+
if ((0, semver_1.major)(yarnVersion) >= 3 && ident.includes(':')) {
|
|
329
|
+
return Buffer.from(ident).toString('base64');
|
|
330
|
+
}
|
|
331
|
+
return ident;
|
|
332
|
+
}
|
|
333
|
+
function applyTls(env, rcFiles, effectiveRegistry, yarnVersion) {
|
|
334
|
+
const legacy = (0, semver_1.lt)(yarnVersion, BERRY_ENV_PARSER_REWRITE);
|
|
335
|
+
const v4 = (0, semver_1.major)(yarnVersion) >= 4;
|
|
336
|
+
// The other major's CA-setting name aborts berry, as an rc setting and as a
|
|
337
|
+
// YARN_* env var alike, so only the name the running major accepts has
|
|
338
|
+
// anything left to reproduce.
|
|
339
|
+
const caKey = v4 ? 'httpsCaFilePath' : 'caFilePath';
|
|
340
|
+
const caEnvKey = v4 ? 'YARN_HTTPS_CA_FILE_PATH' : 'YARN_CA_FILE_PATH';
|
|
341
|
+
let host;
|
|
342
|
+
try {
|
|
343
|
+
host = new URL(effectiveRegistry).hostname;
|
|
344
|
+
}
|
|
345
|
+
catch { }
|
|
346
|
+
// Berry's getNetworkSettings falls back to a global setting only after the
|
|
347
|
+
// matching per-host entries, so a per-host value outranks the YARN_* env vars
|
|
348
|
+
// while those still outrank the rc files.
|
|
349
|
+
const network = resolveNetworkSettings(rcFiles, host, v4);
|
|
350
|
+
// Berry expands env vars in path settings, then resolves an rc-sourced path
|
|
351
|
+
// relative to that rc file's directory and an env-sourced one relative to the
|
|
352
|
+
// directory yarn was invoked from.
|
|
353
|
+
const resolvePath = (key, envKey) => {
|
|
354
|
+
const path = network[key] ?? envPath(process.env[envKey]) ?? firstPathIn(rcFiles, key);
|
|
355
|
+
return path
|
|
356
|
+
? (0, path_1.resolve)(path.baseDir, expandBerryEnvVars(path.value, legacy))
|
|
357
|
+
: undefined;
|
|
358
|
+
};
|
|
359
|
+
const cafile = resolvePath(caKey, caEnvKey);
|
|
360
|
+
if (cafile) {
|
|
361
|
+
(0, utils_1.setCafile)(env, cafile);
|
|
362
|
+
}
|
|
363
|
+
if ((0, semver_1.gte)(yarnVersion, BERRY_CLIENT_CERT_SETTINGS)) {
|
|
364
|
+
// Berry sets cert and key independently (httpUtils has no joint gate); a
|
|
365
|
+
// lone half only fails there because Node cannot present a certificate
|
|
366
|
+
// without its key, so it stays out of the overlay.
|
|
367
|
+
const certfile = resolvePath('httpsCertFilePath', 'YARN_HTTPS_CERT_FILE_PATH');
|
|
368
|
+
const keyfile = resolvePath('httpsKeyFilePath', 'YARN_HTTPS_KEY_FILE_PATH');
|
|
369
|
+
if (certfile && keyfile) {
|
|
370
|
+
(0, utils_1.setClientCertificate)(env, effectiveRegistry, certfile, keyfile);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const enableNetwork = network.enableNetwork ??
|
|
374
|
+
process.env['YARN_ENABLE_NETWORK'] ??
|
|
375
|
+
firstDefinedIn(rcFiles, (c) => c.enableNetwork);
|
|
376
|
+
if (host &&
|
|
377
|
+
enableNetwork !== undefined &&
|
|
378
|
+
isBerryFalseBoolean(enableNetwork)) {
|
|
379
|
+
warnDisabledNetwork(host);
|
|
380
|
+
}
|
|
381
|
+
const strictSsl = process.env['YARN_ENABLE_STRICT_SSL'] ??
|
|
382
|
+
firstDefinedIn(rcFiles, (c) => c.enableStrictSsl);
|
|
383
|
+
if (strictSsl !== undefined) {
|
|
384
|
+
(0, utils_1.setStrictSsl)(env, !isBerryFalseBoolean(strictSsl));
|
|
385
|
+
}
|
|
386
|
+
(0, utils_1.setProxies)(env, {
|
|
387
|
+
httpProxy: expandBerryValue(network.httpProxy ??
|
|
388
|
+
process.env['YARN_HTTP_PROXY'] ??
|
|
389
|
+
firstDefinedIn(rcFiles, (c) => c.httpProxy), legacy),
|
|
390
|
+
httpsProxy: expandBerryValue(network.httpsProxy ??
|
|
391
|
+
process.env['YARN_HTTPS_PROXY'] ??
|
|
392
|
+
firstDefinedIn(rcFiles, (c) => c.httpsProxy), legacy),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
let warnedDisabledNetwork = false;
|
|
396
|
+
/**
|
|
397
|
+
* Berry refuses to reach a host it has enableNetwork off for and exits (verified
|
|
398
|
+
* on 4.15.0: the registry is never contacted). The spawned npm has no such
|
|
399
|
+
* setting and will make the request, so say so once rather than reproducing a
|
|
400
|
+
* refusal that would leave the workspace un-migrated.
|
|
401
|
+
*/
|
|
402
|
+
function warnDisabledNetwork(host) {
|
|
403
|
+
if (warnedDisabledNetwork) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
warnedDisabledNetwork = true;
|
|
407
|
+
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.`);
|
|
408
|
+
}
|
|
409
|
+
function envPath(value) {
|
|
410
|
+
return value ? { value, baseDir: process.cwd() } : undefined;
|
|
411
|
+
}
|
|
412
|
+
function firstPathIn(rcFiles, key) {
|
|
413
|
+
for (const file of rcFiles) {
|
|
414
|
+
const value = file.config[key];
|
|
415
|
+
if (value) {
|
|
416
|
+
return { value, baseDir: (0, path_1.dirname)(file.path) };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
const BERRY_PATH_KEYS = [
|
|
422
|
+
'caFilePath',
|
|
423
|
+
'httpsCaFilePath',
|
|
424
|
+
'httpsCertFilePath',
|
|
425
|
+
'httpsKeyFilePath',
|
|
426
|
+
];
|
|
427
|
+
// Reproduces berry getNetworkSettings. Berry builds the merged map lowest
|
|
428
|
+
// priority first and its sort is stable, so two globs of equal length are
|
|
429
|
+
// consulted in that same order.
|
|
430
|
+
function resolveNetworkSettings(rcFiles, host, deepMerge) {
|
|
431
|
+
const result = {};
|
|
432
|
+
if (!host) {
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
const merged = new Map();
|
|
436
|
+
for (const file of [...rcFiles].reverse()) {
|
|
437
|
+
const settings = file.config.networkSettings;
|
|
438
|
+
if (!settings) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const baseDir = (0, path_1.dirname)(file.path);
|
|
442
|
+
for (const [hostKey, entry] of Object.entries(settings)) {
|
|
443
|
+
// Walking lowest priority first, a later file is the higher-priority one:
|
|
444
|
+
// under v4 each sub-key it defines wins, and under v2/v3 it owns the whole
|
|
445
|
+
// entry, so a lower file never contributes a missing sub-key there.
|
|
446
|
+
const m = (deepMerge && merged.get(hostKey)) || {};
|
|
447
|
+
for (const key of BERRY_PATH_KEYS) {
|
|
448
|
+
if (entry[key] != null) {
|
|
449
|
+
m[key] = { value: entry[key], baseDir };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (entry.enableNetwork != null) {
|
|
453
|
+
m.enableNetwork = entry.enableNetwork;
|
|
454
|
+
}
|
|
455
|
+
if (entry.httpProxy != null) {
|
|
456
|
+
m.httpProxy = entry.httpProxy;
|
|
457
|
+
}
|
|
458
|
+
if (entry.httpsProxy != null) {
|
|
459
|
+
m.httpsProxy = entry.httpsProxy;
|
|
460
|
+
}
|
|
461
|
+
merged.set(hostKey, m);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const matching = [...merged.entries()]
|
|
465
|
+
.filter(([key]) => matchesHostGlob(host, key))
|
|
466
|
+
.sort((a, b) => b[0].length - a[0].length)
|
|
467
|
+
.map(([, m]) => m);
|
|
468
|
+
for (const m of matching) {
|
|
469
|
+
for (const key of BERRY_PATH_KEYS) {
|
|
470
|
+
result[key] ??= m[key];
|
|
471
|
+
}
|
|
472
|
+
result.enableNetwork ??= m.enableNetwork;
|
|
473
|
+
result.httpProxy ??= m.httpProxy;
|
|
474
|
+
result.httpsProxy ??= m.httpsProxy;
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Berry matches a networkSettings key against the hostname with micromatch,
|
|
480
|
+
* which parses two forms differently from minimatch: a bare `(a|b)` is an
|
|
481
|
+
* extglob alternation rather than literal parentheses, and a leading `!` negates
|
|
482
|
+
* the pattern only when it is not the start of a `!(...)` extglob. Left to
|
|
483
|
+
* minimatch a `!(...)` key matches nearly every host, which would hand one
|
|
484
|
+
* host's CA and proxy to the registry.
|
|
485
|
+
*/
|
|
486
|
+
function matchesHostGlob(host, key) {
|
|
487
|
+
return (0, minimatch_1.minimatch)(host, key.replace(/(^|[^\\@!+*?])\(/g, '$1@('), {
|
|
488
|
+
nonegate: key.startsWith('!('),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
// Berry's miscUtils.replaceEnvVariables, applied to every string setting:
|
|
492
|
+
// ${VAR}, ${VAR-default} (default when unset) and ${VAR:-default} (default when
|
|
493
|
+
// unset or empty). Berry throws on an undefined bare ${VAR}, which aborts berry
|
|
494
|
+
// itself, so a working workspace never has one; we leave the reference literal
|
|
495
|
+
// rather than failing the migrate.
|
|
496
|
+
// see https://github.com/yarnpkg/berry/blob/c5857bdee5737425b879492db5e2732a5e6e14f2/packages/yarnpkg-core/sources/miscUtils.ts#L473
|
|
497
|
+
function expandBerryEnvVars(value, legacy, env = process.env) {
|
|
498
|
+
return legacy
|
|
499
|
+
? expandBerryEnvVarsLegacy(value, env)
|
|
500
|
+
: scanBerryEnv(value, 0, env, false).text;
|
|
501
|
+
}
|
|
502
|
+
// Berry's pre-4.13 miscUtils.replaceEnvVariables.
|
|
503
|
+
// See https://github.com/yarnpkg/berry/blob/%40yarnpkg/cli/4.12.0/packages/yarnpkg-core/sources/miscUtils.ts
|
|
504
|
+
function expandBerryEnvVarsLegacy(value, env) {
|
|
505
|
+
return value.replace(/\\?\$\{(?<variableName>[\d\w_]+)(?<colon>:)?(?:-(?<fallback>[^}]*))?\}/g, (match, ...args) => {
|
|
506
|
+
if (match.startsWith('\\')) {
|
|
507
|
+
return match.slice(1);
|
|
508
|
+
}
|
|
509
|
+
const { variableName, colon, fallback } = args[args.length - 1];
|
|
510
|
+
const resolved = env[variableName];
|
|
511
|
+
if (resolved || (Object.hasOwn(env, variableName) && !colon)) {
|
|
512
|
+
return resolved;
|
|
513
|
+
}
|
|
514
|
+
return fallback ?? match;
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
// Scans from `start`, expanding env-var references. When `nested` is set the
|
|
518
|
+
// scan also stops just after the unescaped `}` that closes an enclosing default.
|
|
519
|
+
function scanBerryEnv(input, start, env, nested) {
|
|
520
|
+
let text = '';
|
|
521
|
+
let i = start;
|
|
522
|
+
while (i < input.length) {
|
|
523
|
+
const c = input[i];
|
|
524
|
+
if (c === '\\' && i + 1 < input.length && '\\$}'.includes(input[i + 1])) {
|
|
525
|
+
text += input[i + 1];
|
|
526
|
+
i += 2;
|
|
527
|
+
}
|
|
528
|
+
else if (nested && c === '}') {
|
|
529
|
+
return { text, end: i + 1 };
|
|
530
|
+
}
|
|
531
|
+
else if (c === '$' && input[i + 1] === '{') {
|
|
532
|
+
const ref = parseBerryRef(input, i, env);
|
|
533
|
+
if (ref) {
|
|
534
|
+
text += ref.text;
|
|
535
|
+
i = ref.end;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// Malformed `${...` (berry would throw); emit it literally and continue.
|
|
539
|
+
text += '${';
|
|
540
|
+
i += 2;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
text += c;
|
|
545
|
+
i += 1;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return { text, end: i };
|
|
549
|
+
}
|
|
550
|
+
// Parses one ${NAME} / ${NAME-default} / ${NAME:-default} at `start`, which
|
|
551
|
+
// must be the `${`. The default is scanned brace-balanced so a nested ${...} is
|
|
552
|
+
// captured whole.
|
|
553
|
+
function parseBerryRef(input, start, env) {
|
|
554
|
+
const nameMatch = input.slice(start + 2).match(/^[a-zA-Z]\w*/);
|
|
555
|
+
if (!nameMatch) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
const name = nameMatch[0];
|
|
559
|
+
let i = start + 2 + name.length;
|
|
560
|
+
const value = env[name];
|
|
561
|
+
if (input[i] === '}') {
|
|
562
|
+
// ${NAME}: an undefined value aborts berry; leave the reference literal.
|
|
563
|
+
return {
|
|
564
|
+
text: value !== undefined ? value : input.slice(start, i + 1),
|
|
565
|
+
end: i + 1,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
let emptyIsUnset = false;
|
|
569
|
+
if (input[i] === ':' && input[i + 1] === '-') {
|
|
570
|
+
emptyIsUnset = true;
|
|
571
|
+
i += 2;
|
|
572
|
+
}
|
|
573
|
+
else if (input[i] === '-') {
|
|
574
|
+
i += 1;
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
// Parse (and expand) the default region even when the value wins, so `end`
|
|
580
|
+
// skips past the matching close brace.
|
|
581
|
+
const fallback = scanBerryEnv(input, i, env, true);
|
|
582
|
+
const useValue = emptyIsUnset
|
|
583
|
+
? value !== undefined && value !== ''
|
|
584
|
+
: value !== undefined;
|
|
585
|
+
return {
|
|
586
|
+
text: useValue ? value : fallback.text,
|
|
587
|
+
end: fallback.end,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
// Berry's miscUtils.parseBoolean false set for SettingsType.BOOLEAN.
|
|
591
|
+
function isBerryFalseBoolean(value) {
|
|
592
|
+
return value === false || value === 0 || value === 'false' || value === '0';
|
|
593
|
+
}
|
|
594
|
+
// Its true set. Berry throws on a value in neither, which aborts berry itself,
|
|
595
|
+
// so each side keeps the tolerance that is safe for its setting: an unparseable
|
|
596
|
+
// enableStrictSsl keeps TLS verification on, an unparseable npmAlwaysAuth leaves
|
|
597
|
+
// an unscoped fetch unauthenticated.
|
|
598
|
+
function isBerryTrueBoolean(value) {
|
|
599
|
+
return value === true || value === 1 || value === 'true' || value === '1';
|
|
600
|
+
}
|
|
601
|
+
function expandBerryValue(value, legacy) {
|
|
602
|
+
return value === undefined ? undefined : expandBerryEnvVars(value, legacy);
|
|
603
|
+
}
|
|
604
|
+
function firstDefinedIn(rcFiles, read) {
|
|
605
|
+
for (const file of rcFiles) {
|
|
606
|
+
const value = read(file.config);
|
|
607
|
+
if (value !== undefined) {
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|