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.
Files changed (128) hide show
  1. package/dist/release/changelog-renderer/index.js +4 -5
  2. package/dist/release/index.d.ts +1 -1
  3. package/dist/src/command-line/add/add.js +5 -0
  4. package/dist/src/command-line/affected/affected.js +1 -0
  5. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  6. package/dist/src/command-line/import/import.js +1 -2
  7. package/dist/src/command-line/init/init-v2.js +4 -5
  8. package/dist/src/command-line/migrate/migrate.d.ts +4 -0
  9. package/dist/src/command-line/migrate/migrate.js +89 -26
  10. package/dist/src/command-line/release/changelog.js +5 -0
  11. package/dist/src/command-line/release/index.d.ts +1 -1
  12. package/dist/src/command-line/release/utils/git.js +1 -1
  13. package/dist/src/command-line/release/utils/release-graph.d.ts +17 -0
  14. package/dist/src/command-line/release/utils/release-graph.js +71 -19
  15. package/dist/src/command-line/release/utils/remote-release-clients/github.js +6 -0
  16. package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
  17. package/dist/src/command-line/release/version/release-group-processor.js +80 -6
  18. package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
  19. package/dist/src/command-line/release/version/version-actions.js +9 -1
  20. package/dist/src/command-line/release/version.js +9 -5
  21. package/dist/src/command-line/reset/reset.js +6 -1
  22. package/dist/src/command-line/run/run-one.js +1 -0
  23. package/dist/src/command-line/run-many/run-many.js +1 -0
  24. package/dist/src/command-line/watch/watch.js +13 -0
  25. package/dist/src/core/graph/main.js +1 -1
  26. package/dist/src/core/graph/styles.js +1 -1
  27. package/dist/src/daemon/client/client.d.ts +52 -1
  28. package/dist/src/daemon/client/client.js +160 -36
  29. package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
  30. package/dist/src/daemon/client/daemon-environment.js +110 -2
  31. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  32. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  33. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  34. package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
  35. package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
  36. package/dist/src/daemon/server/latest-nx.js +5 -2
  37. package/dist/src/daemon/server/server.js +24 -0
  38. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  39. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  40. package/dist/src/daemon/socket-utils.d.ts +1 -0
  41. package/dist/src/daemon/socket-utils.js +24 -3
  42. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  43. package/dist/src/daemon/tmp-dir.js +437 -15
  44. package/dist/src/devkit-internals.d.ts +93 -3
  45. package/dist/src/devkit-internals.js +192 -5
  46. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  47. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  48. package/dist/src/generators/tree.d.ts +4 -2
  49. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
  50. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
  51. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
  52. package/dist/src/native/index.d.ts +10 -0
  53. package/dist/src/native/index.js +71 -28
  54. package/dist/src/native/native-bindings.js +1 -0
  55. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  56. package/dist/src/native/native-file-cache-location.js +86 -12
  57. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  58. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  59. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  60. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  61. package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
  62. package/dist/src/plugins/js/lock-file/pnpm-parser.js +60 -0
  63. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
  64. package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
  65. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
  66. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  67. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  68. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  69. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  70. package/dist/src/project-graph/error-types.d.ts +12 -1
  71. package/dist/src/project-graph/error-types.js +27 -2
  72. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  73. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
  74. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  75. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  76. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  77. package/dist/src/project-graph/project-graph.js +20 -0
  78. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +5 -1
  79. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +133 -3
  80. package/dist/src/tasks-runner/task-env.js +5 -2
  81. package/dist/src/tasks-runner/utils.js +2 -6
  82. package/dist/src/utils/assert-workspace-validity.js +7 -2
  83. package/dist/src/utils/fileutils.d.ts +5 -0
  84. package/dist/src/utils/fileutils.js +7 -2
  85. package/dist/src/utils/git-worktrees.d.ts +30 -0
  86. package/dist/src/utils/git-worktrees.js +212 -0
  87. package/dist/src/utils/ignore.d.ts +128 -1
  88. package/dist/src/utils/ignore.js +182 -12
  89. package/dist/src/utils/long-running-target.d.ts +10 -0
  90. package/dist/src/utils/long-running-target.js +19 -0
  91. package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
  92. package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
  93. package/dist/src/utils/min-release-age/packument.js +1 -1
  94. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  95. package/dist/src/utils/nx-tmp-dir.js +40 -0
  96. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  97. package/dist/src/utils/owned-private-dir.js +323 -0
  98. package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
  99. package/dist/src/utils/package-manager-config/bunfig.js +49 -0
  100. package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
  101. package/dist/src/utils/package-manager-config/npmrc.js +124 -0
  102. package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
  103. package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
  104. package/dist/src/utils/package-manager.d.ts +21 -2
  105. package/dist/src/utils/package-manager.js +244 -47
  106. package/dist/src/utils/provenance.js +6 -14
  107. package/dist/src/utils/registry-config/bun.d.ts +2 -0
  108. package/dist/src/utils/registry-config/bun.js +257 -0
  109. package/dist/src/utils/registry-config/index.d.ts +13 -0
  110. package/dist/src/utils/registry-config/index.js +102 -0
  111. package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
  112. package/dist/src/utils/registry-config/pnpm.js +1551 -0
  113. package/dist/src/utils/registry-config/utils.d.ts +195 -0
  114. package/dist/src/utils/registry-config/utils.js +496 -0
  115. package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
  116. package/dist/src/utils/registry-config/yarn-berry.js +612 -0
  117. package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
  118. package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
  119. package/dist/src/utils/safe-spawn.d.ts +24 -0
  120. package/dist/src/utils/safe-spawn.js +104 -0
  121. package/dist/src/utils/shell-quoting.d.ts +15 -0
  122. package/dist/src/utils/shell-quoting.js +35 -0
  123. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  124. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  125. package/migrations.json +6 -0
  126. package/package.json +12 -12
  127. package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
  128. package/dist/src/utils/min-release-age/npmrc.js +0 -45
@@ -0,0 +1,1001 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getYarnClassicSpawnRegistryEnv = getYarnClassicSpawnRegistryEnv;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ const fileutils_1 = require("../fileutils");
8
+ const npmrc_1 = require("../package-manager-config/npmrc");
9
+ const utils_1 = require("./utils");
10
+ /*
11
+ * yarn classic (1.x) registry resolution (verified on 1.22.22):
12
+ *
13
+ * Config files, highest precedence first (yarn merges them earlier-wins):
14
+ * project .{npmrc,yarnrc} > home .{npmrc,yarnrc} > <globalPrefix>/etc/{npmrc,
15
+ * yarnrc} > ancestor .{npmrc,yarnrc} walking up to the filesystem root.
16
+ * `home` is os.homedir(), except as root (uid 0, no FAKEROOTKEY) where yarn
17
+ * reads /usr/local/share first and the real home second. <globalPrefix> is
18
+ * $PREFIX, else dirname(dirname(process.execPath)) (dirname on Windows).
19
+ *
20
+ * Unscoped registry: a `--registry`/`--install.registry` line in a CLI-rc file
21
+ * (yarn injects it as a default CLI arg) > npm_config_registry env >
22
+ * YARN_REGISTRY env > .npmrc registry (the npm-config chain is exhausted first)
23
+ * > .yarnrc registry > https://registry.yarnpkg.com.
24
+ *
25
+ * Scoped: @scope:registry in npm_config_ env > yarn_ env (both merged into the
26
+ * npm registry's config above its files, any casing, a non-leading `_` read as
27
+ * `-`; a dotted key never resolves from env because mergeEnv nests it) >
28
+ * .npmrc > .yarnrc > the unscoped chain. An empty env value masks the file
29
+ * chain of its tier. npm still reads a spelling yarn ignored or masked (a
30
+ * dotted env key, or the .npmrc entry behind an empty env value npm skips), so
31
+ * the overlay then pins yarn's effective registry on the scope.
32
+ *
33
+ * Option keys (cafile, strict-ssl, proxy) resolve the other way around, and off
34
+ * the env first: `yarn_<key>` > .yarnrc > `npm_config_<key>` > .npmrc > yarn's
35
+ * DEFAULTS. A key DEFAULTS carries never reaches the npm tier at all, which is
36
+ * why no npm-config source can turn TLS verification off for yarn. `always-auth`
37
+ * is not one of these: it is read off the npm registry's own config for the
38
+ * registry being queried, not off .yarnrc.
39
+ *
40
+ * npm natively reads the project, home, and <globalPrefix>/etc .npmrc plus env
41
+ * vars identically, so bridging is only needed when a yarn-only surface wins
42
+ * (YARN_REGISTRY, any .yarnrc, ancestor .npmrc, the root /usr/local/share home,
43
+ * or a CLI `--registry` line).
44
+ *
45
+ * See https://github.com/yarnpkg/yarn/blob/740c38c3a962c30ddb344a919bbfb7065620714b/src/registries/npm-registry.js#L345-L436
46
+ */
47
+ const YARN_CLASSIC_DEFAULT_REGISTRY = 'https://registry.yarnpkg.com';
48
+ function getYarnClassicSpawnRegistryEnv(packageName, root) {
49
+ const env = {};
50
+ const scope = (0, utils_1.getPackageScope)(packageName);
51
+ const realHome = (0, os_1.homedir)();
52
+ const { primary, secondary } = yarnHomeTiers(realHome);
53
+ const ancestors = (0, utils_1.ancestorDirectories)(root);
54
+ const etcDir = globalEtcDir();
55
+ // yarn ranks <prefix>/etc above the real home it adds under root, so the
56
+ // secondary home tier follows etc.
57
+ const sources = [
58
+ dotfiles(root, true),
59
+ dotfiles(primary.dir, primary.npmNative),
60
+ {
61
+ npmrcPath: (0, path_1.join)(etcDir, 'npmrc'),
62
+ yarnrcPath: (0, path_1.join)(etcDir, 'yarnrc'),
63
+ npmNative: true,
64
+ },
65
+ ...(secondary ? [dotfiles(secondary.dir, secondary.npmNative)] : []),
66
+ ...ancestors.map((dir) => dotfiles(dir, false)),
67
+ ];
68
+ const npmrcChain = sources.map((s) => ({
69
+ npmNative: s.npmNative,
70
+ map: toYarnValueMap(readChainNpmrcMap(s.npmrcPath)),
71
+ }));
72
+ const cliRcPaths = yarnCliRcPaths(root, ancestors, primary.dir);
73
+ // The two readers overlap on the tiers above without covering each other, and
74
+ // what a file tolerates depends on which of them reach it, so each read is
75
+ // derived from the two path sets rather than from the tier it sits in.
76
+ const lookedUpPaths = new Set(sources.map((s) => s.yarnrcPath));
77
+ const ungatedPaths = new Set(cliRcPaths);
78
+ const yarnrcChain = sources.map((s) => ({
79
+ // npm never reads .yarnrc.
80
+ npmNative: false,
81
+ map: readYarnrcMap(s.yarnrcPath, ungatedPaths.has(s.yarnrcPath) ? 'both' : 'looked-up'),
82
+ }));
83
+ const cliRegistryChain = cliRcPaths.map((rcPath) => ({
84
+ npmNative: false,
85
+ map: readYarnrcMap(rcPath, lookedUpPaths.has(rcPath) ? 'both' : 'cli-rc'),
86
+ }));
87
+ const authRegistry = resolveRegistry(env, npmrcChain, yarnrcChain, cliRegistryChain, scope);
88
+ // yarn tilde-expands paths against userHomeDir.default (the primary home).
89
+ resolveOptions(env, npmrcChain, yarnrcChain, root, primary.dir);
90
+ resolveAuth(env, npmrcChain, scope, authRegistry);
91
+ // Everything above is what yarn itself ends up with: an .npmrc value with its
92
+ // own escape rule applied, and a .yarnrc one it never env-replaces at all.
93
+ // Both can still hold a `${VAR}` the spawned npm would resolve, so escape
94
+ // them back into the text npm hands on unchanged.
95
+ for (const [key, value] of Object.entries(env)) {
96
+ env[key] = (0, utils_1.escapeNpmEnvExpr)(value);
97
+ }
98
+ return env;
99
+ }
100
+ // yarn reads registry auth only from the .npmrc chain, never .yarnrc. Bridge a
101
+ // yarn-only winner only where yarn would send it: bridging unconditionally makes
102
+ // npm authenticate where yarn stays anonymous, and 401 on a registry that serves
103
+ // the package without credentials.
104
+ function resolveAuth(env, npmrcChain, scope, authRegistry) {
105
+ const prefixes = registryOptionPrefixes(authRegistry);
106
+ const requestDart = (0, utils_1.requestNerfDart)(authRegistry);
107
+ // always-auth is read for the registry yarn is about to query, not for the
108
+ // dart a credential came from.
109
+ const authenticates = scope !== null || alwaysAuthFor(prefixes, npmrcChain);
110
+ if (!authenticates) {
111
+ if (requestDart) {
112
+ (0, utils_1.warnNativeCredential)(env, requestDart, 'yarn', 'yarn does send it for scoped packages, and for any registry with always-auth set, so removing it from .npmrc would stop those from authenticating too.', (key) => {
113
+ const match = firstString(npmrcChain, key);
114
+ return ((0, utils_1.readNpmConfigEnv)(process.env, key) ??
115
+ (match?.npmNative ? match.value : undefined));
116
+ });
117
+ }
118
+ return;
119
+ }
120
+ if (!requestDart) {
121
+ return;
122
+ }
123
+ // Only what the ladder itself resolved is carried over. yarn picks a
124
+ // credential per registry, never per URL: whatever request it is about to
125
+ // make, the header is getAuthByRegistry's answer for the registry it resolved,
126
+ // and a key matching the request URL only decides whether to send one at all.
127
+ // So any other nerf-darted key in the chain is one yarn would not have read,
128
+ // and copying it over verbatim would only put it on npm's own walk.
129
+ //
130
+ // getAuthByRegistry runs the whole ladder for one form before it tries the
131
+ // next, so a bare token outranks a registry-scoped basic pair, and the pair
132
+ // only counts when both halves resolve. npm honors auth in the nerf-darted
133
+ // form alone, so whichever form wins is re-keyed onto the dart. Re-keying a
134
+ // bare key out of a file npm reads for itself is not wasted either: npm
135
+ // refuses to run on a bare auth key in its own config (ERR_INVALID_AUTH,
136
+ // --force included) before any overlay matters. The _auth/_password base64
137
+ // carries over as-is.
138
+ let winner;
139
+ for (let rank = 0; rank < CREDENTIAL_FORMS.length && !winner; rank++) {
140
+ const form = CREDENTIAL_FORMS[rank];
141
+ const values = [];
142
+ for (const key of form) {
143
+ const match = resolveCredential(prefixes, npmrcChain, key);
144
+ if (!match) {
145
+ break;
146
+ }
147
+ values.push(match);
148
+ }
149
+ if (values.length === form.length) {
150
+ winner = { form, rank, values };
151
+ }
152
+ }
153
+ if (!winner) {
154
+ return;
155
+ }
156
+ // npm already holds this at the dart it starts its own walk from, so writing
157
+ // it there would only restate it.
158
+ if (!winner.values.every((match) => match.prefix === requestDart && match.npmNative)) {
159
+ winner.form.forEach((key, index) => {
160
+ env[`npm_config_${requestDart}:${key}`] = winner.values[index].value;
161
+ });
162
+ }
163
+ // yarn sends this same credential to any other host it already holds one for.
164
+ // requestNeedsAuth only asks whether some key covers the URL about to be
165
+ // fetched, a tarball served off the registry's path or host among them, and
166
+ // the header stays getAuthByRegistry's answer for the resolved registry. npm
167
+ // reads that host's own key instead, so the winner is written there too.
168
+ //
169
+ // Every declared dart under a gated one gets it, not just the gating dart:
170
+ // npm settles on the deepest declared dart covering the URL, so leaving a
171
+ // deeper one alone would let it answer in the gated one's place. Within a
172
+ // dart, only a form npm ranks above the winner has to be cancelled.
173
+ const outranking = CREDENTIAL_FORMS.slice(0, winner.rank).flat();
174
+ for (const dart of gatedAuthDarts(npmrcChain, requestDart)) {
175
+ winner.form.forEach((key, index) => {
176
+ env[`npm_config_${dart}:${key}`] = winner.values[index].value;
177
+ });
178
+ for (const key of outranking) {
179
+ // Read across npm's own tiers rather than yarn's: a yarn-only file
180
+ // declaring this key first hides an npm-native one that npm still reads.
181
+ const native = (0, utils_1.readNpmConfigEnv)(process.env, `${dart}:${key}`) ??
182
+ npmNativeValue(npmrcChain, `${dart}:${key}`);
183
+ if (native !== undefined) {
184
+ // npm reads this one before the form yarn picked and would answer with
185
+ // the host's own credential. An empty value leaves npm reading the
186
+ // file's; the `null` literal is what cancels it.
187
+ env[`npm_config_${dart}:${key}`] = 'null';
188
+ }
189
+ }
190
+ }
191
+ }
192
+ /** The value the spawned npm reads for `key` from a file it opens itself. */
193
+ function npmNativeValue(npmrcChain, key) {
194
+ for (const file of npmrcChain) {
195
+ const value = file.npmNative ? file.map?.get(key) : undefined;
196
+ if (typeof value === 'string') {
197
+ return value;
198
+ }
199
+ }
200
+ return undefined;
201
+ }
202
+ function dartParts(dart) {
203
+ try {
204
+ // The dart is protocol-relative; lending it one lets the URL parser
205
+ // lower-case the host, which is what makes `//CDN.example.com/` answer for
206
+ // a request npm keys as `//cdn.example.com/`.
207
+ const url = new URL(`http:${dart}`);
208
+ // yarn matches on a path that still carries the query, so a query-bearing
209
+ // key only covers a request whose own sorted query extends it, which a
210
+ // tarball URL's never does; dropping the dart reproduces that. A fragment
211
+ // is different: normalize-url deletes it, so the dart matches on the path
212
+ // in front of it, which url.pathname already is.
213
+ if (url.search) {
214
+ return null;
215
+ }
216
+ // The parser also rewrites a host yarn's own normalization leaves alone, a
217
+ // legacy IPv4 spelling (`127.1`) or an IDN among them. Matching on the
218
+ // rewritten one would open a host yarn never opened, so anything the parse
219
+ // did not leave as it found it is dropped instead.
220
+ if (dart.slice(2).split(/[/#]/)[0].toLowerCase() !== url.hostname) {
221
+ return null;
222
+ }
223
+ // normalize-url's order: collapse, decode, then the trailing slash. A
224
+ // malformed escape throws out of decodeURI into the catch below, which is
225
+ // where yarn's own normalize call would have thrown too.
226
+ const foldPath = decodeURI(url.pathname.replace(/\/{2,}/g, '/'));
227
+ return {
228
+ host: url.host,
229
+ matchHost: url.hostname.replace(/\.$/, '').replace(/^www\./, ''),
230
+ path: url.pathname,
231
+ foldPath,
232
+ matchPath: foldPath.replace(/(?!^)\/$/, ''),
233
+ };
234
+ }
235
+ catch {
236
+ return null;
237
+ }
238
+ }
239
+ /**
240
+ * Every dart the overlay has to answer for once yarn's requestNeedsAuth opens a
241
+ * host up: the gating darts themselves, and any dart declaring a credential
242
+ * underneath one, which npm would otherwise settle on first.
243
+ *
244
+ * Both sides are compared the way yarn compares them, host against host and
245
+ * then path against path. Run as one string the relation would read
246
+ * `//h.example` as covering `//h.example.evil` and hand that origin the
247
+ * registry's credential. The path stays a plain prefix test, which is yarn's
248
+ * own. The registry's dart is left out, since the winner already goes there.
249
+ */
250
+ function gatedAuthDarts(npmrcChain, requestDart) {
251
+ const gates = [];
252
+ const declared = [];
253
+ for (const file of npmrcChain) {
254
+ if (!file.map) {
255
+ continue;
256
+ }
257
+ for (const key of file.map.keys()) {
258
+ const parts = key.split(':');
259
+ if (!parts[0].startsWith('//')) {
260
+ continue;
261
+ }
262
+ const dart = dartParts(parts[0]);
263
+ if (!dart) {
264
+ continue;
265
+ }
266
+ // yarn's own condition, precedence included: the length test binds to
267
+ // `_authToken` alone. So a host carrying a port, which puts that port
268
+ // where the key name has to be, gates through neither.
269
+ if ((parts.length === 2 && parts[1] === '_authToken') ||
270
+ parts[1] === '_password') {
271
+ gates.push(dart);
272
+ }
273
+ if (parts.length === 2 && AUTH_KEYS.has(parts[1])) {
274
+ declared.push(dart);
275
+ }
276
+ }
277
+ }
278
+ const darts = new Set();
279
+ for (const gate of gates) {
280
+ for (const dart of [gate, ...declared]) {
281
+ if (dart.matchHost !== gate.matchHost ||
282
+ !dart.matchPath.startsWith(gate.matchPath)) {
283
+ continue;
284
+ }
285
+ // The folding means one dart can answer for two spellings on each side,
286
+ // host and path, and which ones the fetch uses is not known here, so
287
+ // every pairing goes in: npm walks the tarball URL's own spelling, so a
288
+ // key folded away from it is only found where the walk lands.
289
+ for (const host of new Set([dart.host, dart.matchHost])) {
290
+ for (const path of new Set([dart.path, dart.foldPath])) {
291
+ const key = `//${host}${path}`;
292
+ if (key !== requestDart) {
293
+ darts.add(key);
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ return darts;
300
+ }
301
+ /**
302
+ * The config-key prefixes yarn's NpmRegistry.getRegistryOption tries, in its
303
+ * order: the registry itself, then the same without the protocol, then without
304
+ * a trailing `registry` segment, recursing into each. It never climbs a path
305
+ * the way npm does, so the directory above the registry is reachable only
306
+ * through that suffix rewrite, which carries no segment boundary of its own: a
307
+ * `.../myregistry/` registry strips to `.../my/`.
308
+ */
309
+ function registryOptionPrefixes(registry) {
310
+ const prefixes = [];
311
+ const visit = (candidate) => {
312
+ const prefix = candidate.endsWith('/') ? candidate : `${candidate}/`;
313
+ if (prefixes.includes(prefix)) {
314
+ return;
315
+ }
316
+ prefixes.push(prefix);
317
+ if (/^https?:/i.test(prefix)) {
318
+ visit(prefix.replace(/^https?:/i, ''));
319
+ }
320
+ if (/registry\/?$/.test(prefix)) {
321
+ visit(prefix.replace(/registry\/?$/, ''));
322
+ }
323
+ };
324
+ visit(registry);
325
+ return prefixes;
326
+ }
327
+ // getRegistryOrGlobalOption chains the ladder and then the bare key with `||`,
328
+ // so an empty value falls through rather than settling the lookup.
329
+ function resolveCredential(prefixes, npmrcChain, key) {
330
+ for (const prefix of prefixes) {
331
+ const match = firstString(npmrcChain, `${prefix}:${key}`);
332
+ if (match?.value) {
333
+ return { ...match, prefix };
334
+ }
335
+ }
336
+ const bare = firstString(npmrcChain, key);
337
+ return bare?.value ? { ...bare, prefix: null } : undefined;
338
+ }
339
+ // .yarnrc never feeds always-auth: yarn reads it from NpmRegistry's own config,
340
+ // walking its registry ladder before the bare key. Unlike resolveOption this
341
+ // ignores npmNative, since it drives the auth gate rather than a bridge, and it
342
+ // chains with `||`, so a falsy rung falls through to the next.
343
+ function alwaysAuthFor(prefixes, npmrcChain) {
344
+ for (const prefix of prefixes) {
345
+ const key = `${prefix}:always-auth`;
346
+ if (yarnConfigValue(key, envReachable(key) ? key : null, npmrcChain)) {
347
+ return true;
348
+ }
349
+ }
350
+ // The bare key's env spelling carries the underscore mergeEnv rewrites into
351
+ // the dash an rc file spells out.
352
+ return Boolean(yarnConfigValue('always-auth', 'always_auth', npmrcChain));
353
+ }
354
+ // mergeEnv lowercases the whole variable name and stores it through objectPath,
355
+ // which splits on `.` into nested objects while every read is flat, so a
356
+ // registry-scoped env key only reaches yarn dot-free and already lowercase (a
357
+ // key for //localhost:PORT/ authenticates an unscoped fetch, the same key for
358
+ // //127.0.0.1:PORT/ does not).
359
+ function envReachable(key) {
360
+ return !key.includes('.') && key === key.toLowerCase();
361
+ }
362
+ // yarn merges the `yarn_` env prefix before any rc file is read and then
363
+ // `npm_config_` over it into the same flat map, so an env value replaces the
364
+ // file's at that key and npm's spelling replaces yarn's.
365
+ function yarnConfigValue(key, envName, npmrcChain) {
366
+ if (envName !== null) {
367
+ const fromEnv = (0, utils_1.readEnvVar)(process.env, `npm_config_${envName}`) ??
368
+ (0, utils_1.readEnvVar)(process.env, `yarn_${envName}`);
369
+ if (fromEnv !== undefined) {
370
+ return normalizeYarnConfigValue(fromEnv);
371
+ }
372
+ }
373
+ return firstDefined(npmrcChain, key)?.value;
374
+ }
375
+ /** Mirrors yarn's BaseRegistry.normalizeConfigOption. */
376
+ function normalizeYarnConfigValue(value) {
377
+ return value === 'true' ? true : value === 'false' ? false : value;
378
+ }
379
+ // getAuthByRegistry's order: a bearer token, else a basic one, else the
380
+ // username/password pair, which it only sends when both halves resolve.
381
+ const CREDENTIAL_FORMS = [
382
+ ['_authToken'],
383
+ ['_auth'],
384
+ ['username', '_password'],
385
+ ];
386
+ // Every key npm's own hasAuth reads a dart as authenticated on. The client
387
+ // certificate pair is in because npm settles on the deepest dart carrying any
388
+ // of these, even though it ranks the pair below all three forms above.
389
+ const AUTH_KEYS = new Set([...CREDENTIAL_FORMS.flat(), 'certfile', 'keyfile']);
390
+ function resolveRegistry(env, npmrcChain, yarnrcChain, cliYarnrcChain, scope) {
391
+ const scopedRegistry = scope
392
+ ? resolveScopedRegistry(env, npmrcChain, yarnrcChain, scope)
393
+ : undefined;
394
+ const unscopedRegistry = resolveUnscopedRegistry(env, npmrcChain, yarnrcChain, cliYarnrcChain);
395
+ if (scope) {
396
+ pinScopedRegistry(env, npmrcChain, scope, scopedRegistry ?? unscopedRegistry);
397
+ }
398
+ // yarn's default is npmjs' CNAME and npm stays on registry.npmjs.org, so the
399
+ // dart lands where npm queries.
400
+ return scopedRegistry ?? unscopedRegistry ?? 'https://registry.npmjs.org/';
401
+ }
402
+ // npm reads scoped spellings yarn cannot: a dotted env key mergeEnv nests out
403
+ // of yarn's flat lookups, and the .npmrc entry behind an empty env value, which
404
+ // masks the file for yarn while npm skips the empty. Wherever the resolution
405
+ // above left the scoped key unclaimed and npm's own read (env, else its native
406
+ // .npmrc chain) lands elsewhere, yarn's effective registry is written over it.
407
+ function pinScopedRegistry(env, npmrcChain, scope, effectiveRegistry) {
408
+ const scopedKey = `${scope}:registry`;
409
+ if (env[`npm_config_${scopedKey}`] !== undefined) {
410
+ return;
411
+ }
412
+ const npmReads = (0, utils_1.readNpmConfigEnv)(process.env, scopedKey) ??
413
+ npmNativeValue(npmrcChain, scopedKey);
414
+ const effective = effectiveRegistry ?? 'https://registry.npmjs.org/';
415
+ if (npmReads !== undefined && npmReads !== effective) {
416
+ (0, utils_1.setScopedRegistry)(env, scope, effective);
417
+ }
418
+ }
419
+ function resolveUnscopedRegistry(env, npmrcChain, yarnrcChain, cliYarnrcChain) {
420
+ // 1. A `--registry`/`--install.registry` line in a CLI-rc .yarnrc lands at
421
+ // yarn's CLI tier, above npm_config_registry env, so it always needs bridging.
422
+ const cliRegistry = firstString(cliYarnrcChain, '--install.registry') ??
423
+ firstString(cliYarnrcChain, '--registry');
424
+ if (cliRegistry) {
425
+ (0, utils_1.setRegistry)(env, cliRegistry.value);
426
+ return cliRegistry.value;
427
+ }
428
+ // 2. npm_config_registry env: npm resolves it natively.
429
+ const npmConfigRegistry = (0, utils_1.readEnvVar)(process.env, 'npm_config_registry');
430
+ if (npmConfigRegistry !== undefined) {
431
+ return npmConfigRegistry;
432
+ }
433
+ // 3. YARN_REGISTRY env (yarn-only).
434
+ const yarnRegistryEnv = (0, utils_1.readEnvVar)(process.env, 'YARN_REGISTRY');
435
+ if (yarnRegistryEnv !== undefined) {
436
+ (0, utils_1.setRegistry)(env, yarnRegistryEnv);
437
+ return yarnRegistryEnv;
438
+ }
439
+ // 4. The .npmrc chain is exhausted before .yarnrc is consulted.
440
+ const npmrcRegistry = firstString(npmrcChain, 'registry');
441
+ if (npmrcRegistry) {
442
+ if (!npmrcRegistry.npmNative) {
443
+ (0, utils_1.setRegistry)(env, npmrcRegistry.value);
444
+ }
445
+ return npmrcRegistry.value;
446
+ }
447
+ // 5. .yarnrc registry (every entry yarn-only). The yarn default is npmjs'
448
+ // CNAME; leaving npm on registry.npmjs.org keeps nerf-darted auth working.
449
+ const yarnrcRegistry = firstString(yarnrcChain, 'registry');
450
+ if (yarnrcRegistry &&
451
+ yarnrcRegistry.value.replace(/\/$/, '') !== YARN_CLASSIC_DEFAULT_REGISTRY) {
452
+ (0, utils_1.setRegistry)(env, yarnrcRegistry.value);
453
+ return yarnrcRegistry.value;
454
+ }
455
+ return undefined;
456
+ }
457
+ // The value yarn's mergeEnv resolves for `key` under `prefix`: the whole env
458
+ // key is lowercased, the prefix stripped, `__` becomes `.` and a non-leading
459
+ // `_` becomes `-`, later entries overwriting earlier ones, an empty value
460
+ // included. mergeEnv writes through objectPath.set, which nests a dotted key
461
+ // while yarn's lookups stay flat, so a dotted key resolves from no spelling.
462
+ function readYarnMergedEnv(prefix, key) {
463
+ if (key.includes('.')) {
464
+ return undefined;
465
+ }
466
+ let value;
467
+ for (const [envKey, candidate] of Object.entries(process.env)) {
468
+ if (candidate === undefined) {
469
+ continue;
470
+ }
471
+ const lowerKey = envKey.toLowerCase();
472
+ if (!lowerKey.startsWith(prefix)) {
473
+ continue;
474
+ }
475
+ const normalized = lowerKey
476
+ .slice(prefix.length)
477
+ .replace(/__/g, '.')
478
+ .replace(/([^_])_/g, '$1-');
479
+ if (normalized === key) {
480
+ value = candidate;
481
+ }
482
+ }
483
+ return value;
484
+ }
485
+ // Returns the scoped registry yarn resolves even when npm reads it natively, so
486
+ // auth can dart onto it.
487
+ function resolveScopedRegistry(env, npmrcChain, yarnrcChain, scope) {
488
+ const scopedKey = `${scope}:registry`;
489
+ // yarn merges yarn_ env into the npm registry config at init and npm_config_
490
+ // env over it, with the .npmrc chain under both; the yarn registry tier then
491
+ // holds yarn_ env over the .yarnrc chain. An empty env value still occupies
492
+ // its key, so it masks the file chain of its tier and falls through as unset.
493
+ const npmConfigEnvRegistry = readYarnMergedEnv('npm_config_', scopedKey);
494
+ const yarnEnvRegistry = readYarnMergedEnv('yarn_', scopedKey);
495
+ const npmTierEnvRegistry = npmConfigEnvRegistry ?? yarnEnvRegistry;
496
+ if (npmTierEnvRegistry) {
497
+ if (npmConfigEnvRegistry) {
498
+ // npm reads its own env tier natively unless its key rewrite resolves
499
+ // the spelling to another setting, which is when the bridge takes over.
500
+ if ((0, utils_1.readNpmConfigEnv)(process.env, scopedKey) !== npmConfigEnvRegistry) {
501
+ (0, utils_1.setScopedRegistry)(env, scope, npmConfigEnvRegistry);
502
+ }
503
+ }
504
+ else {
505
+ (0, utils_1.setScopedRegistry)(env, scope, npmTierEnvRegistry);
506
+ }
507
+ return npmTierEnvRegistry;
508
+ }
509
+ if (npmTierEnvRegistry === undefined) {
510
+ const npmScoped = firstString(npmrcChain, scopedKey);
511
+ if (npmScoped) {
512
+ if (!npmScoped.npmNative) {
513
+ (0, utils_1.setScopedRegistry)(env, scope, npmScoped.value);
514
+ }
515
+ return npmScoped.value;
516
+ }
517
+ }
518
+ // The yarn registry tier: an npm-tier empty still reaches it, and a truthy
519
+ // yarn_ value only gets here masked behind an empty npm_config_ spelling.
520
+ if (yarnEnvRegistry) {
521
+ (0, utils_1.setScopedRegistry)(env, scope, yarnEnvRegistry);
522
+ return yarnEnvRegistry;
523
+ }
524
+ if (yarnEnvRegistry === undefined) {
525
+ const yarnScoped = firstString(yarnrcChain, scopedKey);
526
+ if (yarnScoped) {
527
+ (0, utils_1.setScopedRegistry)(env, scope, yarnScoped.value);
528
+ return yarnScoped.value;
529
+ }
530
+ }
531
+ return undefined;
532
+ }
533
+ function resolveOptions(env, npmrcChain, yarnrcChain, root, home) {
534
+ const cafile = yarnEnvOption('cafile') ??
535
+ resolveOption(firstString, npmrcChain, yarnrcChain, 'cafile');
536
+ if (cafile) {
537
+ (0, utils_1.setCafile)(env, resolveYarnPath(cafile, root, home));
538
+ }
539
+ // DEFAULTS carries `strict-ssl`, so no npm-config source reaches yarn's value
540
+ // (an .npmrc `strict-ssl=false` and `npm_config_strict_ssl=false` both leave
541
+ // verification on). That cuts both ways: npm also has to be told to keep
542
+ // verifying where its own config would stop, so a declared value is bridged in
543
+ // either direction. An env value arrives as a string and yarn coerces the bare
544
+ // booleans out of it first, so `YARN_STRICT_SSL=false` does turn verification
545
+ // off where a quoted `"false"` in a file does not.
546
+ const strictSslEnv = yarnEnvOption('strict-ssl');
547
+ const strictSsl = strictSslEnv !== undefined
548
+ ? normalizeYarnConfigValue(strictSslEnv)
549
+ : firstDefined(yarnrcChain, 'strict-ssl')?.value;
550
+ if (strictSsl !== undefined &&
551
+ truthyStrictSsl(strictSsl) !== npmVerifiesTls(npmrcChain)) {
552
+ (0, utils_1.setStrictSsl)(env, truthyStrictSsl(strictSsl));
553
+ }
554
+ (0, utils_1.setProxies)(env, {
555
+ httpProxy: yarnEnvOption('proxy') ??
556
+ resolveOption(firstString, npmrcChain, yarnrcChain, 'proxy'),
557
+ httpsProxy: yarnEnvOption('https-proxy') ??
558
+ resolveOption(firstString, npmrcChain, yarnrcChain, 'https-proxy'),
559
+ });
560
+ }
561
+ /**
562
+ * The `yarn_` env tier for an option key (YARN_CAFILE, YARN_STRICT_SSL, ...).
563
+ * yarn merges it before any rc file is read and getOption consults yarn's own
564
+ * config before npm's, so it outranks every file and a `npm_config_` value of
565
+ * the same key (with both YARN_CAFILE and npm_config_cafile set, `yarn config
566
+ * get cafile` returns the YARN_ one). npm cannot see it under that name.
567
+ */
568
+ function yarnEnvOption(key) {
569
+ return (0, utils_1.readEnvVar)(process.env, `yarn_${key.replace(/-/g, '_')}`);
570
+ }
571
+ // Yarn's option resolution below its own env tier: .yarnrc, then npm's config.
572
+ // Returns only a value that needs bridging, so a native .npmrc winner comes back
573
+ // undefined.
574
+ function resolveOption(lookup, npmrcChain, yarnrcChain, key) {
575
+ const yarnrc = lookup(yarnrcChain, key);
576
+ if (yarnrc) {
577
+ return yarnrc.value;
578
+ }
579
+ // The `npm_config_` env tier outranks every .npmrc and npm reads it itself, so
580
+ // a value there needs no bridge and shadows the file chain below it.
581
+ if ((0, utils_1.readNpmConfigEnv)(process.env, key) !== undefined) {
582
+ return undefined;
583
+ }
584
+ const npmrc = lookup(npmrcChain, key);
585
+ return npmrc && !npmrc.npmNative ? npmrc.value : undefined;
586
+ }
587
+ function dotfiles(dir, npmNative) {
588
+ return {
589
+ npmrcPath: (0, path_1.join)(dir, '.npmrc'),
590
+ yarnrcPath: (0, path_1.join)(dir, '.yarnrc'),
591
+ npmNative,
592
+ };
593
+ }
594
+ // yarn's userHomeDir.default is /usr/local/share when running as root (uid 0, no
595
+ // FAKEROOTKEY) and the real home otherwise; under root the real home stays on as
596
+ // a second tier. The primary is the tilde-expansion base, and npm reads only the
597
+ // real home natively.
598
+ function yarnHomeTiers(home) {
599
+ const isRoot = process.platform !== 'win32' &&
600
+ typeof process.getuid === 'function' &&
601
+ process.getuid() === 0 &&
602
+ !process.env.FAKEROOTKEY;
603
+ if (isRoot) {
604
+ return {
605
+ primary: { dir: '/usr/local/share', npmNative: false },
606
+ secondary: { dir: home, npmNative: true },
607
+ };
608
+ }
609
+ return { primary: { dir: home, npmNative: true } };
610
+ }
611
+ /**
612
+ * The files yarn injects `--`-prefixed lines from as default CLI args. It
613
+ * collects them through getRcPaths, a wider set than the tiers the registry
614
+ * client reads, and merges them last-wins, so this list runs the other way
615
+ * round: highest precedence first, to be read first-wins.
616
+ * See https://github.com/yarnpkg/yarn/blob/740c38c3a962c30ddb344a919bbfb7065620714b/src/util/rc.js#L11-L62
617
+ */
618
+ function yarnCliRcPaths(root, ancestors, primaryHome) {
619
+ const paths = [];
620
+ if (process.env.YARN_CONFIG) {
621
+ paths.push(process.env.YARN_CONFIG);
622
+ }
623
+ // Read straight off the env here rather than through os.homedir(), and every
624
+ // home tier dropped when it is unset, both as yarn does it.
625
+ const home = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
626
+ if (home) {
627
+ paths.push((0, path_1.join)(home, '.yarnrc.yml'), (0, path_1.join)(home, '.yarnrc'), (0, path_1.join)(home, '.yarn', 'config'), (0, path_1.join)(home, '.config', 'yarn'), (0, path_1.join)(home, '.config', 'yarn', 'config'), yarnConfigDir(primaryHome));
628
+ }
629
+ if (process.platform !== 'win32') {
630
+ // The literal /etc, not the <prefix>/etc tier the registry client reads.
631
+ paths.push((0, path_1.join)('/etc', 'yarnrc'), (0, path_1.join)('/etc', 'yarn', 'config'));
632
+ }
633
+ // A .yarnrc.yml sibling rides along with every .yarnrc yarn names, and lands
634
+ // above it.
635
+ for (const dir of [root, ...ancestors]) {
636
+ paths.push((0, path_1.join)(dir, '.yarnrc.yml'), (0, path_1.join)(dir, '.yarnrc'));
637
+ }
638
+ return paths;
639
+ }
640
+ // Mirrors yarn's getConfigDir, which resolves against userHomeDir: the same
641
+ // root-aware home yarnHomeTiers picks as the primary tier.
642
+ function yarnConfigDir(primaryHome) {
643
+ if (process.platform === 'win32') {
644
+ return process.env.LOCALAPPDATA
645
+ ? (0, path_1.join)(process.env.LOCALAPPDATA, 'Yarn', 'Config')
646
+ : (0, path_1.join)(primaryHome, '.config', 'yarn');
647
+ }
648
+ return process.env.XDG_CONFIG_HOME
649
+ ? (0, path_1.join)(process.env.XDG_CONFIG_HOME, 'yarn')
650
+ : (0, path_1.join)(primaryHome, '.config', 'yarn');
651
+ }
652
+ // Mirrors yarn's getGlobalPrefix.
653
+ function globalEtcDir() {
654
+ // Falsy rather than absent, so an exported but empty PREFIX falls through to
655
+ // the executable's own prefix instead of resolving `etc` against the cwd.
656
+ if (process.env.PREFIX) {
657
+ return (0, path_1.join)(process.env.PREFIX, 'etc');
658
+ }
659
+ if (process.platform === 'win32') {
660
+ return (0, path_1.join)((0, path_1.dirname)(process.execPath), 'etc');
661
+ }
662
+ const prefix = (0, path_1.dirname)((0, path_1.dirname)(process.execPath));
663
+ // DESTDIR reroots the prefix on Unix only, and only once PREFIX has passed.
664
+ return (0, path_1.join)(process.env.DESTDIR ? (0, path_1.join)(process.env.DESTDIR, prefix) : prefix, 'etc');
665
+ }
666
+ function firstDefined(chain, key) {
667
+ for (const file of chain) {
668
+ const value = file.map?.get(key);
669
+ if (value !== undefined) {
670
+ return { value, npmNative: file.npmNative };
671
+ }
672
+ }
673
+ return undefined;
674
+ }
675
+ // yarn stores a bare `false`/number as a non-string, never a valid
676
+ // registry/path/proxy value, so skip those entries rather than coercing.
677
+ function firstString(chain, key) {
678
+ for (const file of chain) {
679
+ const value = file.map?.get(key);
680
+ if (typeof value === 'string') {
681
+ return { value, npmNative: file.npmNative };
682
+ }
683
+ }
684
+ return undefined;
685
+ }
686
+ // yarn computes strictSSL as Boolean(getOption('strict-ssl')), so a quoted
687
+ // `"false"` (a string) keeps verification on.
688
+ function truthyStrictSsl(value) {
689
+ return Boolean(value);
690
+ }
691
+ // Only a literal `false` turns npm's own strict-ssl off, and toYarnValueMap has
692
+ // already typed that as a boolean.
693
+ function npmVerifiesTls(npmrcChain) {
694
+ const fromEnv = (0, utils_1.readNpmConfigEnv)(process.env, 'strict-ssl');
695
+ if (fromEnv !== undefined) {
696
+ return fromEnv !== 'false';
697
+ }
698
+ const native = firstDefined(npmrcChain.filter((file) => file.npmNative), 'strict-ssl');
699
+ return native?.value !== false;
700
+ }
701
+ // yarn expands a leading `~/` to the home dir, then path.resolve()s against the
702
+ // cwd (the workspace root). Resolve to an absolute path since the spawned npm
703
+ // may run from a temp dir.
704
+ function resolveYarnPath(value, root, home) {
705
+ if (value === '~') {
706
+ return home;
707
+ }
708
+ if (value.startsWith('~/') || value.startsWith('~\\')) {
709
+ return (0, path_1.resolve)(home, value.slice(2));
710
+ }
711
+ return (0, path_1.resolve)(root, value);
712
+ }
713
+ // yarn looks each .npmrc in its chain up before opening it, and unlike .yarnrc
714
+ // nothing reads it a second time ungated, so whatever the lookup misses counts
715
+ // as absent and only a file it finds and cannot open aborts yarn.
716
+ function readChainNpmrcMap(path) {
717
+ if (!(0, fs_1.existsSync)(path)) {
718
+ return null;
719
+ }
720
+ const map = (0, npmrc_1.readNpmrcMap)(path);
721
+ if (map === 'unreadable') {
722
+ // No resolution left to reproduce. Reading on without the file would
723
+ // resolve the registry from the remaining ones, silently landing on an
724
+ // ancestor's or the default.
725
+ throw new Error(`The .npmrc at ${path} could not be read.`);
726
+ }
727
+ return map;
728
+ }
729
+ function toYarnValueMap(map) {
730
+ if (!map) {
731
+ return null;
732
+ }
733
+ // yarn env-replaces `${VAR}` in .npmrc values itself, and with a grammar of
734
+ // its own, so expand here rather than leaving the spawned npm to apply its.
735
+ // The ini reader yields strings; coercing the bare booleans lines these values
736
+ // up with the .yarnrc side.
737
+ const result = new Map();
738
+ for (const [key, value] of map) {
739
+ result.set(key, normalizeYarnConfigValue((0, utils_1.expandYarnEnvVars)(value)));
740
+ }
741
+ return result;
742
+ }
743
+ /**
744
+ * Parses one of yarn classic's rc files into a last-write-wins map. Yarn reads
745
+ * it with its lockfile parser first, so one rejected line costs the whole file
746
+ * rather than just that line, then retries the whole file with js-yaml and
747
+ * honors what the retry accepts, which is how `registry: https://host/` works
748
+ * despite the lockfile grammar throwing on it. A `.yml` skips straight to the
749
+ * retry's parser.
750
+ * See https://github.com/yarnpkg/yarn/blob/740c38c3a962c30ddb344a919bbfb7065620714b/src/lockfile/parse.js#L384-L409
751
+ *
752
+ * @yarnpkg/lockfile on npm is a 2018 snapshot of that parser and has since
753
+ * diverged: its name token excludes `.`, so it rejects the `cafile ./ca.pem`
754
+ * that yarn 1.22 accepts. Reading with it would drop whole files yarn honors.
755
+ */
756
+ function readYarnrcMap(path, readers) {
757
+ // Whatever the lookup misses is absent to yarn as well, but only where no
758
+ // second reader opens the file behind its back.
759
+ if (readers === 'looked-up' && !(0, fs_1.existsSync)(path)) {
760
+ return null;
761
+ }
762
+ // Unconditional even for a `.yml`, which parses from the path below: this
763
+ // open is what classifies the fault, and neither parser carries a tolerance.
764
+ let raw;
765
+ try {
766
+ raw = (0, fs_1.readFileSync)(path, 'utf-8');
767
+ }
768
+ catch (e) {
769
+ // The CLI-arg pass spares ENOENT and EISDIR. A file the registry client
770
+ // also reads keeps only ENOENT, since a directory passes its lookup and
771
+ // then dies on the open. Anything left leaves no resolution to reproduce.
772
+ // See https://github.com/yarnpkg/yarn/blob/740c38c3a962c30ddb344a919bbfb7065620714b/src/util/rc.js#L64-L79
773
+ if (e?.code === 'ENOENT' ||
774
+ (e?.code === 'EISDIR' && readers === 'cli-rc')) {
775
+ return null;
776
+ }
777
+ throw new Error(`The yarn config at ${path} could not be read.`);
778
+ }
779
+ // A .yml goes to the failsafe YAML schema alone, with no lockfile grammar in
780
+ // front and no retry behind, which is why only a mapping declares anything.
781
+ if (path.endsWith('.yml')) {
782
+ const map = parseYarnrcAsYaml(path);
783
+ // yarn keeps `yarnPath` alone from a .yml that names one, dropping the CLI
784
+ // args read here.
785
+ // See https://github.com/yarnpkg/yarn/blob/740c38c3a962c30ddb344a919bbfb7065620714b/src/rc.js#L55-L70
786
+ return typeof map.get('yarnPath') === 'string' ? new Map() : map;
787
+ }
788
+ try {
789
+ return parseYarnrc(raw);
790
+ }
791
+ catch {
792
+ return parseYarnrcAsYaml(path);
793
+ }
794
+ }
795
+ /**
796
+ * Yarn's own fallback for an rc file its lockfile parser rejects, and its only
797
+ * parser for a `.yml`. The failsafe schema makes every scalar a string, and
798
+ * classic passes the schema alone where berry also passes `json: true`, so a
799
+ * duplicate key throws here rather than resolving last-wins.
800
+ */
801
+ function parseYarnrcAsYaml(path) {
802
+ let loaded;
803
+ try {
804
+ loaded = (0, fileutils_1.readYamlFile)(path, { failsafe: true });
805
+ }
806
+ catch {
807
+ // Rejected by both parsers, where yarn rethrows the first error and dies
808
+ // rather than reading on without the file. Keep the parse error out of the
809
+ // message: it quotes the lines around the fault, which here can be
810
+ // credential material.
811
+ throw new Error(`The yarn config at ${path} could not be read.`);
812
+ }
813
+ const map = new Map();
814
+ // Yarn ignores a document that is not a mapping rather than failing, which is
815
+ // how an unquoted `registry https://host/` (one bare scalar to YAML) ends up
816
+ // declaring nothing.
817
+ if (!loaded || typeof loaded !== 'object' || Array.isArray(loaded)) {
818
+ return map;
819
+ }
820
+ for (const [key, value] of Object.entries(loaded)) {
821
+ // Anything the failsafe schema did not make a string is a nested block, and
822
+ // nothing read here is one. A `false` arriving as the truthy string 'false'
823
+ // is yarn's own behavior, not a loss.
824
+ if (typeof value === 'string') {
825
+ map.set(key, value);
826
+ }
827
+ }
828
+ return map;
829
+ }
830
+ /**
831
+ * Yarn's lockfile tokenizer. A bare word stops at `:`, which is why an unquoted
832
+ * URL value breaks the file: its `://` splits into three tokens.
833
+ */
834
+ function* tokenizeYarnrc(input) {
835
+ let lastNewline = false;
836
+ while (input.length) {
837
+ let chop = 0;
838
+ if (input[0] === '\n' || input[0] === '\r') {
839
+ chop = input[0] === '\r' && input[1] === '\n' ? 2 : 1;
840
+ yield { type: 'newline' };
841
+ }
842
+ else if (input[0] === '#') {
843
+ const end = input.indexOf('\n');
844
+ chop = end === -1 ? input.length : end;
845
+ }
846
+ else if (input[0] === ' ') {
847
+ if (lastNewline) {
848
+ let size = 1;
849
+ while (input[size] === ' ') {
850
+ size++;
851
+ }
852
+ if (size % 2) {
853
+ throw new Error('Invalid number of spaces');
854
+ }
855
+ chop = size;
856
+ yield { type: 'indent', value: size / 2 };
857
+ }
858
+ else {
859
+ chop = 1;
860
+ }
861
+ }
862
+ else if (input[0] === '"') {
863
+ let i = 1;
864
+ for (; i < input.length; i++) {
865
+ if (input[i] === '"' &&
866
+ !(input[i - 1] === '\\' && input[i - 2] !== '\\')) {
867
+ i++;
868
+ break;
869
+ }
870
+ }
871
+ chop = i;
872
+ try {
873
+ yield { type: 'value', value: JSON.parse(input.slice(0, i)) };
874
+ }
875
+ catch {
876
+ yield { type: 'invalid' };
877
+ }
878
+ }
879
+ else if (/^[0-9]/.test(input)) {
880
+ const digits = /^[0-9]+/.exec(input)[0];
881
+ chop = digits.length;
882
+ yield { type: 'value', value: +digits };
883
+ }
884
+ else if (input.startsWith('true')) {
885
+ chop = 4;
886
+ yield { type: 'value', value: true };
887
+ }
888
+ else if (input.startsWith('false')) {
889
+ chop = 5;
890
+ yield { type: 'value', value: false };
891
+ }
892
+ else if (input[0] === ':') {
893
+ chop = 1;
894
+ yield { type: 'colon' };
895
+ }
896
+ else if (input[0] === ',') {
897
+ chop = 1;
898
+ yield { type: 'comma' };
899
+ }
900
+ else if (/^[a-zA-Z/.-]/.test(input)) {
901
+ let i = 0;
902
+ while (i < input.length && !': \n\r,'.includes(input[i])) {
903
+ i++;
904
+ }
905
+ chop = i;
906
+ yield { type: 'value', value: input.slice(0, i) };
907
+ }
908
+ else {
909
+ yield { type: 'invalid' };
910
+ }
911
+ if (!chop) {
912
+ throw new Error('Made no progress');
913
+ }
914
+ lastNewline = input[0] === '\n' || (input[0] === '\r' && input[1] === '\n');
915
+ input = input.slice(chop);
916
+ }
917
+ yield { type: 'eof' };
918
+ }
919
+ /**
920
+ * Yarn's lockfile parser, flattened: only top-level scalar settings are kept,
921
+ * since nothing read here is an object, but a nested block is still parsed so
922
+ * its presence does not discard the settings around it. Throws on anything
923
+ * yarn's parser rejects.
924
+ */
925
+ function parseYarnrc(raw) {
926
+ // Yarn strips a UTF-8 BOM before tokenising (parse.js stripBOM); left in
927
+ // place it breaks the first name token, dropping the file to the YAML retry,
928
+ // which declares nothing for the yarn-grammar shape.
929
+ if (raw.charCodeAt(0) === 0xfeff) {
930
+ raw = raw.slice(1);
931
+ }
932
+ const tokens = tokenizeYarnrc(raw);
933
+ const map = new Map();
934
+ const next = () => tokens.next().value;
935
+ let token = next();
936
+ const parseLevel = (indent, keep) => {
937
+ while (true) {
938
+ if (token.type === 'newline') {
939
+ token = next();
940
+ if (!indent) {
941
+ continue;
942
+ }
943
+ if (token.type !== 'indent') {
944
+ return;
945
+ }
946
+ if (token.value !== indent) {
947
+ return;
948
+ }
949
+ token = next();
950
+ }
951
+ else if (token.type === 'indent') {
952
+ if (token.value !== indent) {
953
+ return;
954
+ }
955
+ token = next();
956
+ }
957
+ else if (token.type === 'eof') {
958
+ return;
959
+ }
960
+ else if (token.type === 'value') {
961
+ const keys = [String(token.value)];
962
+ token = next();
963
+ while (token.type === 'comma') {
964
+ token = next();
965
+ if (token.type !== 'value') {
966
+ throw new Error('Expected string');
967
+ }
968
+ keys.push(String(token.value));
969
+ token = next();
970
+ }
971
+ const wasColon = token.type === 'colon';
972
+ if (wasColon) {
973
+ token = next();
974
+ }
975
+ if (token.type === 'value') {
976
+ const value = token.value;
977
+ if (keep) {
978
+ for (const key of keys) {
979
+ map.set(key, value);
980
+ }
981
+ }
982
+ token = next();
983
+ }
984
+ else if (wasColon) {
985
+ parseLevel(indent + 1, false);
986
+ if (indent && token.type !== 'indent') {
987
+ return;
988
+ }
989
+ }
990
+ else {
991
+ throw new Error('Invalid value type');
992
+ }
993
+ }
994
+ else {
995
+ throw new Error(`Unknown token: ${token.type}`);
996
+ }
997
+ }
998
+ };
999
+ parseLevel(0, true);
1000
+ return map;
1001
+ }