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,1551 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPnpmSpawnRegistryEnv = getPnpmSpawnRegistryEnv;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ const semver_1 = require("semver");
8
+ const pnpm_config_1 = require("../package-manager-config/pnpm-config");
9
+ const npmrc_1 = require("../package-manager-config/npmrc");
10
+ const fileutils_1 = require("../fileutils");
11
+ const logger_1 = require("../logger");
12
+ const utils_1 = require("./utils");
13
+ /*
14
+ * pnpm registry resolution, by version line:
15
+ *
16
+ * - < 10.6.0: registry config lives only in the .npmrc chain and npm_config_*
17
+ * env vars, which a spawned npm resolves identically on its own. Nothing to
18
+ * bridge.
19
+ * - 10.6.0 - 10.x: pnpm-workspace.yaml accepts every .npmrc setting in
20
+ * camelCase (https://github.com/pnpm/pnpm/pull/9211) and the parsed yaml
21
+ * object is Object.assign-ed over the npmrc-derived config, so a
22
+ * `registries` map (default/@scope keys) wholesale-replaces the
23
+ * npmrc/env/CLI registry selection. That config keeps npm's own tiers plus a
24
+ * `workspace` one, the .npmrc beside the workspace manifest, ranked under the
25
+ * project .npmrc and over the user one.
26
+ * - >= 11.0.0: the config reader merges per key: registries =
27
+ * {...fromNpmrc, ...fromYaml}, then `pnpm_config_registry` env overrides
28
+ * only `registries.default`. npm_config_* env vars are no longer read (11.6.0
29
+ * restores the URL-scoped `//<dart>:<key>` ones alone), and .npmrc is
30
+ * restricted to auth/registry/network keys. The per-package lookup is
31
+ * registries[scope] ?? registries.default. An `auth.ini` file in pnpm's
32
+ * config dir layers between the user and workspace .npmrc. Because pnpm
33
+ * ignores npm_config_* here, the overlay this builds is consumed by the
34
+ * spawned `npm pack` (and a forced `npm view`), not by `pnpm view`, which
35
+ * resolves natively.
36
+ * - >= 11.10.0: a JSON auth tier (`pnpm_config__auth` env over the global
37
+ * config.yaml `_auth`) layers credentials above the URL-scoped env tier and
38
+ * registries above the yaml.
39
+ */
40
+ const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
41
+ const BARE_AUTH_KEYS = [
42
+ '_authToken',
43
+ '_auth',
44
+ 'username',
45
+ '_password',
46
+ ];
47
+ // pnpm's UNSCOPED_RESCOPABLE_KEYS: the credentials above plus the client TLS
48
+ // material and the helper, all of which it pins to the declaring file's own
49
+ // registry rather than leaving unscoped.
50
+ const PNPM_RESCOPABLE_KEYS = [
51
+ ...BARE_AUTH_KEYS,
52
+ 'tokenHelper',
53
+ 'cert',
54
+ 'key',
55
+ ];
56
+ /**
57
+ * pnpm's normalize-registry-url as it reads from 11.15.1: a trailing slash on
58
+ * whatever the URL parser makes of the value. Before that it appended one only
59
+ * to a URL with no path at all, which nerfDart already resolves the same way, so
60
+ * only this form needs reproducing.
61
+ */
62
+ function normalizePnpmRegistry(registry) {
63
+ let normalized = registry;
64
+ try {
65
+ normalized = new URL(registry).toString();
66
+ }
67
+ catch {
68
+ // Left as written, the way pnpm leaves it; nerfDart rejects it next.
69
+ }
70
+ return normalized.endsWith('/') ? normalized : `${normalized}/`;
71
+ }
72
+ /**
73
+ * The nerf dart pnpm keys a registry's own settings on. Normalizing first moves
74
+ * it a path segment deeper than npm's plain dart: `https://h/api/npm` keys on
75
+ * `//h/api/npm/`, not on its parent `//h/api/`, so an unscoped credential pinned
76
+ * there no longer reaches a sibling path. 10.x still bundles the older
77
+ * normalizer through 10.34.5, where the plain dart is what pnpm computes.
78
+ */
79
+ function pnpmNerfDart(registry, pnpmVersion) {
80
+ return (0, utils_1.nerfDart)((0, semver_1.gte)(pnpmVersion, '11.15.1') ? normalizePnpmRegistry(registry) : registry);
81
+ }
82
+ /**
83
+ * pnpm's workspaceIsTrustedAuthFile: the workspace .npmrc doubling as the file
84
+ * pnpm authenticates from. It then expands the references it withholds from a
85
+ * project-controlled file. pnpm reads the coincident path at both tiers and
86
+ * merges them, which the workspace copy wins outright, so it is read once here.
87
+ */
88
+ function isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath) {
89
+ return (0, path_1.resolve)(workspaceDir, '.npmrc') === userConfigPath;
90
+ }
91
+ function getPnpmSpawnRegistryEnv(packageName, root, pnpmVersion) {
92
+ const env = {};
93
+ // Which surfaces this pnpm honors depends on the version, so an undetermined
94
+ // one bridges nothing.
95
+ if (!pnpmVersion || (0, semver_1.lt)(pnpmVersion, '10.6.0')) {
96
+ return env;
97
+ }
98
+ const workspaceFile = findPnpmWorkspaceFile(root, pnpmVersion);
99
+ const settings = readPnpmWorkspaceSettings(workspaceFile, pnpmVersion);
100
+ const scope = (0, utils_1.getPackageScope)(packageName);
101
+ // Kept identical to the predicate the caller hands mergeNpmConfigEnv at spawn
102
+ // time, which drops the bridged ambient npm_config_* this answers true for
103
+ // (settings outside the bridged set stay ambient either way).
104
+ const managerIgnoresEnv = (0, utils_1.ignoresNpmConfigEnv)('pnpm', pnpmVersion);
105
+ if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
106
+ // The replace wipes the npmrc/env/CLI selection outright, so the scoped key
107
+ // is forced to the yaml default when the map has no entry for the scope. A
108
+ // scoped-only map leaves pnpm no default at all, which crashes it on an
109
+ // unscoped target but resolves a scoped one fine, so npm's own default is
110
+ // left in place rather than aimed at a registry pnpm uses only for that
111
+ // scope.
112
+ const yamlDefault = pickYamlRegistry(settings, 'default', workspaceFile);
113
+ if (yamlDefault) {
114
+ (0, utils_1.setRegistry)(env, yamlDefault);
115
+ }
116
+ const pick = scope
117
+ ? (pickYamlRegistry(settings, scope, workspaceFile) ?? yamlDefault)
118
+ : undefined;
119
+ if (scope && pick) {
120
+ (0, utils_1.setScopedRegistry)(env, scope, pick);
121
+ }
122
+ // Both .npmrc files pnpm reads here, project first, which is the order both
123
+ // the bridge and the bypass list resolve them in.
124
+ const npmrcPaths = pnpmNpmrcPaths(root, workspaceFile);
125
+ // On this version line pnpm's user config is npm's own (no auth.ini, no
126
+ // npmrcAuthFile), always a file npm reads for itself, so it is a tier the
127
+ // derived settings resolve across rather than one to bridge.
128
+ const userConfigPath = getNpmUserConfigPath(root);
129
+ const npmrcProxies = bridgeWorkspaceNpmrc(env, npmrcPaths, userConfigPath, scope, pnpmVersion);
130
+ // auth.ini is an 11.x file, so the .npmrc chain is the whole of what can
131
+ // need its bypass list re-spelled here.
132
+ bridgeNoProxy(env, [...npmrcPaths, userConfigPath], pnpmVersion);
133
+ // Applied last: pnpm assigns the yaml over the whole npmrc-derived config,
134
+ // so what it declares outranks everything the files above contributed.
135
+ applyYamlNetworkSettings(env, settings);
136
+ applyResolvedProxies(env, [settings, npmrcProxies], root, scope, managerIgnoresEnv);
137
+ reportCredentialDivergences(env, root, scope, userConfigPath, npmrcPaths.map((path) => ({ path, filtered: false })), pnpmVersion, managerIgnoresEnv);
138
+ return env;
139
+ }
140
+ // The yaml-only keys go in at npm's env tier, where npm's per-key chain
141
+ // reproduces pnpm's ordering: a project .npmrc @scope:registry still beats an
142
+ // injected default, while an injected @scope:registry beats the project
143
+ // .npmrc scoped key (yaml @scope > npmrc @scope in pnpm). JSON-auth
144
+ // registries sit above the yaml and below the named env registry, which pnpm
145
+ // applies onto registries.default after every spread.
146
+ const jsonAuth = readJsonAuthTier(pnpmVersion);
147
+ const globalSettings = readPnpmGlobalSettings(pnpmVersion);
148
+ const globalPath = getGlobalConfigPath();
149
+ const scopedRegistry = scope
150
+ ? (jsonAuth?.registries[scope] ??
151
+ pickYamlRegistry(settings, scope, workspaceFile) ??
152
+ pickYamlRegistry(globalSettings, scope, globalPath))
153
+ : undefined;
154
+ if (scope && scopedRegistry) {
155
+ (0, utils_1.setScopedRegistry)(env, scope, scopedRegistry);
156
+ }
157
+ // A top-level `registry` in a yaml file is an explicitly set key, which pnpm
158
+ // applies onto registries.default on its own. Where it does that moved: from
159
+ // 11.5.3 before the workspace file is even read, so only the global file's
160
+ // reaches it and every map still outranks it, and from 11.10.0 again after
161
+ // every map has merged, which puts both files' above the JSON auth tier.
162
+ const lateScalar = (0, semver_1.gte)(pnpmVersion, '11.10.0')
163
+ ? settings.registry || globalSettings.registry
164
+ : undefined;
165
+ const earlyScalar = (0, semver_1.gte)(pnpmVersion, '11.5.3') && (0, semver_1.lt)(pnpmVersion, '11.10.0')
166
+ ? globalSettings.registry
167
+ : undefined;
168
+ const defaultRegistry = readPnpmEnvVar('registry', pnpmVersion) ??
169
+ lateScalar ??
170
+ jsonAuth?.registries['default'] ??
171
+ pickYamlRegistry(settings, 'default', workspaceFile) ??
172
+ earlyScalar ??
173
+ pickYamlRegistry(globalSettings, 'default', globalPath);
174
+ if (defaultRegistry) {
175
+ (0, utils_1.setRegistry)(env, defaultRegistry);
176
+ }
177
+ const authIniPath = getAuthIniPath();
178
+ const userConfigPath = getPnpmUserConfigPath(pnpmVersion, root);
179
+ applyUrlScopedEnvConfig(env, pnpmVersion);
180
+ applyJsonAuthCredentials(env, scope, jsonAuth);
181
+ // From 11 pnpm reads one project .npmrc, and it is the one beside the
182
+ // workspace file it walked up to, not the one in the directory it runs in
183
+ // (loadNpmrcConfig's `workspaceDir ?? localPrefix`). npm reads the latter for
184
+ // itself, so a nested workspace puts the two readers on different files.
185
+ const workspaceDir = workspaceFile ? (0, path_1.dirname)(workspaceFile) : root;
186
+ const npmrcProxies = bridgeNpmrcSources(env, root, workspaceDir, scope, authIniPath, userConfigPath, pnpmVersion, managerIgnoresEnv);
187
+ reportCredentialDivergences(env, root, scope, userConfigPath, [
188
+ {
189
+ path: (0, path_1.join)(workspaceDir, '.npmrc'),
190
+ filtered: !isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath),
191
+ },
192
+ { path: authIniPath, filtered: false },
193
+ ], pnpmVersion, managerIgnoresEnv);
194
+ // resolveNoProxy takes the bypass list across every layer below, so the yaml
195
+ // does not write it here.
196
+ applyYamlNetworkSettings(env, globalSettings, false);
197
+ applyYamlNetworkSettings(env, settings, false);
198
+ applyEnvNetworkSettings(env, pnpmVersion);
199
+ applyResolvedProxies(env, [envProxyDeclarations(pnpmVersion), settings, globalSettings, npmrcProxies], root, scope, managerIgnoresEnv);
200
+ const noProxy = resolveNoProxy(settings, globalSettings, workspaceDir, authIniPath, userConfigPath, pnpmVersion);
201
+ if (noProxy) {
202
+ (0, utils_1.setProxies)(env, { noProxy });
203
+ }
204
+ return env;
205
+ }
206
+ /**
207
+ * pnpm's own env reader: the lowercase prefix, then the uppercase one (which
208
+ * only arrived in 11.0.6), with an empty value counting as undeclared.
209
+ * See readEnvVar in pnpm's config reader.
210
+ */
211
+ function readPnpmEnvVar(key, pnpmVersion) {
212
+ const value = process.env[`pnpm_config_${key}`] ??
213
+ ((0, semver_1.gte)(pnpmVersion, '11.0.6')
214
+ ? process.env[`PNPM_CONFIG_${key.toUpperCase()}`]
215
+ : undefined);
216
+ return value || undefined;
217
+ }
218
+ /**
219
+ * The URL-scoped entries pnpm >= 11.6.0 reads from the environment
220
+ * (readUrlScopedEnvConfig): `p?npm_config_//<dart>:<key>`, case-insensitive
221
+ * prefix, minus `:tokenHelper`, which pnpm refuses to take from it. The
222
+ * `npm_config_` spellings reach the spawned npm ambiently (mergeNpmConfigEnv
223
+ * keeps them for these versions); the `pnpm_config_` spellings are invisible to
224
+ * npm, so re-spell those onto the overlay, which also reproduces pnpm merging
225
+ * its own prefix above npm's for the same dart.
226
+ */
227
+ function applyUrlScopedEnvConfig(env, pnpmVersion) {
228
+ if ((0, semver_1.lt)(pnpmVersion, '11.6.0')) {
229
+ return;
230
+ }
231
+ for (const [key, value] of Object.entries(process.env)) {
232
+ // pnpm skips a null or empty value outright, matching npm's env tier.
233
+ if (!value) {
234
+ continue;
235
+ }
236
+ const match = /^pnpm_config_(\/\/.+)$/i.exec(key);
237
+ if (!match || match[1].endsWith(':tokenHelper')) {
238
+ continue;
239
+ }
240
+ // pnpm takes the value as written, so escape what npm's env tier would
241
+ // otherwise expand out of a credential that carries a `${VAR}` of its own.
242
+ env[`npm_config_${match[1]}`] = (0, utils_1.escapeNpmEnvExpr)(value);
243
+ }
244
+ }
245
+ /**
246
+ * The JSON auth tier pnpm reads from 11.10.0: `pnpm_config__auth` (then the
247
+ * uppercase spelling, an empty value skipped) parsed as JSON, over the global
248
+ * config.yaml's top-level `_auth`, merged per entry with the env winning.
249
+ * Its registries outrank the workspace yaml and lose to the named env/CLI
250
+ * registry; its credentials outrank the URL-scoped env tier and every file.
251
+ * pnpm dies on a declaration it cannot parse, so that throws into the caller's
252
+ * fall-open. See readJsonAuthEnv/parseJsonAuth in pnpm's config reader.
253
+ */
254
+ function readJsonAuthTier(pnpmVersion) {
255
+ if ((0, semver_1.lt)(pnpmVersion, '11.10.0')) {
256
+ return null;
257
+ }
258
+ const raw = process.env['pnpm_config__auth'] ||
259
+ process.env['PNPM_CONFIG__AUTH'] ||
260
+ undefined;
261
+ let envTier = null;
262
+ if (raw !== undefined) {
263
+ let parsed;
264
+ try {
265
+ parsed = JSON.parse(raw);
266
+ }
267
+ catch {
268
+ throw new Error('The pnpm_config__auth environment variable is not valid JSON.');
269
+ }
270
+ envTier = parsePnpmJsonAuth(parsed, 'pnpm_config__auth', pnpmVersion);
271
+ }
272
+ const yamlAuth = readPnpmGlobalConfigYaml()?.['_auth'];
273
+ const yamlTier = yamlAuth != null ? parsePnpmJsonAuth(yamlAuth, '_auth', pnpmVersion) : null;
274
+ if (!envTier && !yamlTier) {
275
+ return null;
276
+ }
277
+ const merged = new Map();
278
+ for (const tier of [yamlTier, envTier]) {
279
+ for (const entry of tier?.auth ?? []) {
280
+ merged.set(`${entry.scope}\0${entry.dart}`, entry);
281
+ }
282
+ }
283
+ return {
284
+ auth: [...merged.values()],
285
+ registries: {
286
+ ...(yamlTier?.registries ?? {}),
287
+ ...(envTier?.registries ?? {}),
288
+ },
289
+ };
290
+ }
291
+ /**
292
+ * pnpm's parseJsonAuth: registry URL over scope, each leaf exactly
293
+ * { authToken: string }, every violation fatal. Messages carry the setting
294
+ * name and entry position rather than the entry itself, since a malformed
295
+ * URL key can embed credentials.
296
+ */
297
+ function parsePnpmJsonAuth(parsed, source, pnpmVersion) {
298
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
299
+ throw new Error(`The pnpm ${source} setting must be a JSON object of registry URLs.`);
300
+ }
301
+ const auth = [];
302
+ const registries = {};
303
+ let entryNumber = 0;
304
+ for (const [rawUrl, scopes] of Object.entries(parsed)) {
305
+ entryNumber++;
306
+ let url;
307
+ try {
308
+ url = new URL(rawUrl);
309
+ }
310
+ catch {
311
+ throw invalidJsonAuthEntry(source, entryNumber);
312
+ }
313
+ if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
314
+ url.hostname === '' ||
315
+ url.username !== '' ||
316
+ url.password !== '' ||
317
+ url.search !== '' ||
318
+ url.hash !== '') {
319
+ throw invalidJsonAuthEntry(source, entryNumber);
320
+ }
321
+ const dart = pnpmNerfDart(url.href, pnpmVersion);
322
+ if (!dart) {
323
+ throw invalidJsonAuthEntry(source, entryNumber);
324
+ }
325
+ if (scopes === null ||
326
+ typeof scopes !== 'object' ||
327
+ Array.isArray(scopes)) {
328
+ throw invalidJsonAuthScopes(source, entryNumber);
329
+ }
330
+ for (const [scope, creds] of Object.entries(scopes)) {
331
+ const validScope = scope === '@' ||
332
+ (scope.startsWith('@') &&
333
+ scope.length > 1 &&
334
+ !scope.includes('/') &&
335
+ !scope.includes(':'));
336
+ if (!validScope ||
337
+ creds === null ||
338
+ typeof creds !== 'object' ||
339
+ Array.isArray(creds) ||
340
+ Object.keys(creds).some((field) => field !== 'authToken') ||
341
+ typeof creds['authToken'] !== 'string') {
342
+ throw invalidJsonAuthScopes(source, entryNumber);
343
+ }
344
+ auth.push({
345
+ dart,
346
+ scope,
347
+ token: creds['authToken'],
348
+ });
349
+ registries[scope === '@' ? 'default' : scope] = url.href;
350
+ }
351
+ }
352
+ return { auth, registries };
353
+ }
354
+ function invalidJsonAuthEntry(source, entryNumber) {
355
+ return new Error(`Entry ${entryNumber} of the pnpm ${source} setting is not a plain http(s) registry URL.`);
356
+ }
357
+ function invalidJsonAuthScopes(source, entryNumber) {
358
+ return new Error(`Entry ${entryNumber} of the pnpm ${source} setting must map scopes ("@" or "@org") to { "authToken": string } objects.`);
359
+ }
360
+ /**
361
+ * npm has no scope-qualified auth key, so a scoped entry lands on the plain
362
+ * dart, and only for the scope of the package being fetched: pnpm would not
363
+ * send that token for anything else. Registry-wide entries go first so a
364
+ * scoped token for the same registry wins, the way pnpm's per-scope credential
365
+ * lookup prefers the specific entry. Every registry in the map is bridged, on
366
+ * the same grounds as the auth.ini dart loop.
367
+ */
368
+ function applyJsonAuthCredentials(env, scope, jsonAuth) {
369
+ if (!jsonAuth) {
370
+ return;
371
+ }
372
+ for (const wanted of scope ? ['@', scope] : ['@']) {
373
+ for (const entry of jsonAuth.auth) {
374
+ if (entry.scope === wanted) {
375
+ // The token sits in a nested object, which pnpm's yaml replacer passes
376
+ // through untouched, so it is escaped rather than expanded here: npm
377
+ // would otherwise resolve a `${VAR}` pnpm sends as written.
378
+ env[`npm_config_${entry.dart}:_authToken`] = (0, utils_1.escapeNpmEnvExpr)(entry.token);
379
+ }
380
+ }
381
+ }
382
+ }
383
+ /**
384
+ * The TLS settings pnpm >= 11 takes from its own `PNPM_CONFIG_*` prefix. They
385
+ * outrank pnpm-workspace.yaml, so they are applied after it. `cafile` is left
386
+ * out on purpose: pnpm accepts it and then never uses it for the fetch, the
387
+ * same dead config as the yaml key.
388
+ */
389
+ function applyEnvNetworkSettings(env, pnpmVersion) {
390
+ const strictSsl = readPnpmEnvVar('strict_ssl', pnpmVersion);
391
+ if (strictSsl !== undefined) {
392
+ // parseField types this Boolean, so only an explicit 'false' turns
393
+ // verification off.
394
+ (0, utils_1.setStrictSsl)(env, strictSsl !== 'false');
395
+ }
396
+ }
397
+ /** The proxy settings pnpm >= 11 takes from its own `PNPM_CONFIG_*` prefix. */
398
+ function envProxyDeclarations(pnpmVersion) {
399
+ return {
400
+ proxy: readPnpmEnvVar('proxy', pnpmVersion),
401
+ httpProxy: readPnpmEnvVar('http_proxy', pnpmVersion),
402
+ httpsProxy: readPnpmEnvVar('https_proxy', pnpmVersion),
403
+ };
404
+ }
405
+ /**
406
+ * The two proxies npm should end up with for `registry`. pnpm resolves each of
407
+ * the three settings across every tier first, and only then falls back from
408
+ * httpsProxy to the legacy proxy and from httpProxy to whichever of those won,
409
+ * so the derivation cannot be done per tier: a workspace file's httpsProxy is
410
+ * what an environment-supplied `proxy` leaves undeclared, and so still wins.
411
+ *
412
+ * npm has no http-only proxy, its `proxy` serving https too when `https-proxy`
413
+ * is unset, so an http-only one is withheld unless http is what npm requests.
414
+ * A value npm already resolves for itself under the same key is left to it.
415
+ */
416
+ function resolveProxies(tiers, registry, npmSees) {
417
+ const declared = (key) => tiers.map((tier) => tier[key]).find(Boolean);
418
+ const httpsProxy = declared('httpsProxy') || declared('proxy');
419
+ const httpProxy = declared('httpProxy') || httpsProxy;
420
+ const send = (value, npmKey) => value === npmSees(npmKey) ? undefined : value;
421
+ return {
422
+ httpProxy: send(httpsProxy || registry.startsWith('http://') ? httpProxy : undefined, 'proxy'),
423
+ httpsProxy: send(httpsProxy, 'https-proxy'),
424
+ };
425
+ }
426
+ /**
427
+ * The proxy-bypass list pnpm >= 11 ends up using. It reads the `no-proxy`
428
+ * spelling and only falls back to `noproxy`, so the spelling decides before the
429
+ * layer does: a workspace .npmrc `no-proxy` beats a pnpm-workspace.yaml
430
+ * `noproxy`. Within one spelling the env sits above the yaml files, the
431
+ * workspace one above the global one, and those above the npmrc-family files,
432
+ * which only the `no-proxy` spelling reaches.
433
+ * See createPackageManagerNetworkConfig in pnpm's config reader.
434
+ */
435
+ function resolveNoProxy(settings, globalSettings, npmrcDir, authIniPath, userConfigPath, pnpmVersion) {
436
+ const envNoProxy = readPnpmEnvVar('no_proxy', pnpmVersion);
437
+ if (envNoProxy) {
438
+ return envNoProxy;
439
+ }
440
+ const yamlNoProxy = settings.noProxy ?? globalSettings.noProxy;
441
+ if (yamlNoProxy) {
442
+ return yamlNoProxy;
443
+ }
444
+ const fromFiles = fileNoProxy([(0, path_1.join)(npmrcDir, '.npmrc'), authIniPath, userConfigPath], pnpmVersion);
445
+ if (fromFiles) {
446
+ return fromFiles;
447
+ }
448
+ return (readPnpmEnvVar('noproxy', pnpmVersion) ??
449
+ settings.noproxy ??
450
+ globalSettings.noproxy);
451
+ }
452
+ /**
453
+ * pnpm looks pnpm-workspace.yaml up before a reader that only tolerates ENOENT,
454
+ * so whatever this misses reads as absent while a file it finds and cannot open
455
+ * aborts the command. 11.8.0 swapped find-up, which requires the match to be a
456
+ * file, for a bare existence check, which is where a directory in the file's
457
+ * place stops being looked past.
458
+ */
459
+ function pnpmFindsWorkspaceFile(path, pnpmVersion) {
460
+ return (0, semver_1.lt)(pnpmVersion, '11.8.0') ? (0, fileutils_1.fileExists)(path) : (0, fs_1.existsSync)(path);
461
+ }
462
+ const WORKSPACE_MANIFEST_FILENAME = 'pnpm-workspace.yaml';
463
+ /**
464
+ * pnpm's INVALID_WORKSPACE_MANIFEST_FILENAME: names near enough the real one
465
+ * that it looks for them alongside it and refuses to walk past one it finds.
466
+ */
467
+ const MISSPELLED_WORKSPACE_MANIFEST_NAMES = [
468
+ 'pnpm-workspaces.yaml',
469
+ 'pnpm-workspaces.yml',
470
+ 'pnpm-workspace.yml',
471
+ ];
472
+ /** 11.0.0 added the dot-prefixed spellings to that list. */
473
+ const MISSPELLED_WORKSPACE_MANIFEST_NAMES_11 = [
474
+ ...MISSPELLED_WORKSPACE_MANIFEST_NAMES,
475
+ '.pnpm-workspace.yaml',
476
+ '.pnpm-workspace.yml',
477
+ '.pnpm-workspaces.yaml',
478
+ '.pnpm-workspaces.yml',
479
+ ];
480
+ /**
481
+ * pnpm resolves pnpm-workspace.yaml by walking up from the directory it runs
482
+ * in and stopping at the nearest hit, so a workspace nested under another one
483
+ * inherits the outer file's settings. Null when no directory on the way up has
484
+ * one.
485
+ * See findWorkspaceDir in pnpm's workspace root finder.
486
+ */
487
+ function findPnpmWorkspaceFile(root, pnpmVersion) {
488
+ // The env var names the directory outright, skipping the walk without
489
+ // checking that the file is there, so a missing one reads as a workspace
490
+ // declaring nothing rather than sending the lookup back up the tree.
491
+ const fromEnv = (0, utils_1.readEnvVar)(process.env, 'NPM_CONFIG_WORKSPACE_DIR');
492
+ if (fromEnv) {
493
+ return (0, path_1.join)((0, path_1.resolve)(root, fromEnv), WORKSPACE_MANIFEST_FILENAME);
494
+ }
495
+ const misspelled = (0, semver_1.lt)(pnpmVersion, '11.0.0')
496
+ ? MISSPELLED_WORKSPACE_MANIFEST_NAMES
497
+ : MISSPELLED_WORKSPACE_MANIFEST_NAMES_11;
498
+ for (const dir of [root, ...(0, utils_1.ancestorDirectories)(root)]) {
499
+ const path = (0, path_1.join)(dir, WORKSPACE_MANIFEST_FILENAME);
500
+ // Looked up first, because pnpm searches the names in this order within a
501
+ // directory and takes the first hit: a correctly named file beside a
502
+ // misspelled one is the one it reads.
503
+ if (pnpmFindsWorkspaceFile(path, pnpmVersion)) {
504
+ return path;
505
+ }
506
+ for (const name of misspelled) {
507
+ const misspelledPath = (0, path_1.join)(dir, name);
508
+ if (pnpmFindsWorkspaceFile(misspelledPath, pnpmVersion)) {
509
+ // pnpm aborts the command here (BAD_WORKSPACE_MANIFEST_NAME) instead of
510
+ // walking on, so there is no resolution left to reproduce. Propagating
511
+ // to the caller's fall-open warns instead of silently resolving against
512
+ // a correctly named file further up that pnpm never reaches.
513
+ throw new Error(`The pnpm workspace manifest file should be named "${WORKSPACE_MANIFEST_FILENAME}". File found: ${misspelledPath}`);
514
+ }
515
+ }
516
+ }
517
+ return null;
518
+ }
519
+ function readPnpmWorkspaceSettings(path, pnpmVersion) {
520
+ if (path === null) {
521
+ return {};
522
+ }
523
+ const doc = (0, pnpm_config_1.readPnpmYamlConfig)(path);
524
+ if (doc === null) {
525
+ return {};
526
+ }
527
+ if (doc === 'unusable') {
528
+ // pnpm aborts on this file, so there is no resolution left to reproduce.
529
+ // Propagating to the caller's fall-open warns instead of silently treating
530
+ // the workspace as declaring no registry.
531
+ throw new Error(`The pnpm workspace file at ${path} could not be read.`);
532
+ }
533
+ return normalizePnpmWorkspaceSettings(resolveYamlEnv(doc, path, pnpmVersion, false), path);
534
+ }
535
+ const PNPM_REQUEST_PROXY_SCALARS = [
536
+ 'httpProxy',
537
+ 'httpsProxy',
538
+ 'noProxy',
539
+ 'proxy',
540
+ 'noproxy',
541
+ ];
542
+ const PNPM_REQUEST_DESTINATION_SCALARS = new Set(['pnprServer', 'registry']);
543
+ const PNPM_REQUEST_DESTINATION_SCALARS_11_11 = new Set([
544
+ ...PNPM_REQUEST_DESTINATION_SCALARS,
545
+ ...PNPM_REQUEST_PROXY_SCALARS,
546
+ ]);
547
+ const PNPM_REQUEST_DESTINATION_SCALARS_10 = new Set(['registry']);
548
+ const PNPM_REQUEST_DESTINATION_SCALARS_10_34_5 = new Set([
549
+ ...PNPM_REQUEST_DESTINATION_SCALARS_10,
550
+ ...PNPM_REQUEST_PROXY_SCALARS,
551
+ ]);
552
+ /**
553
+ * The scalar settings pnpm withholds from a file rather than expanding a
554
+ * `${VAR}` into them, its REQUEST_DESTINATION_SCALAR_KEYS, null on a line that
555
+ * withholds nothing. It covered the destination alone until 11.11.0 added the
556
+ * proxies that carry a request there, and it was backported onto the 10 line,
557
+ * where it never names the pnpr server and picked the proxies up in 10.34.5.
558
+ *
559
+ * Each line is decided before the next is consulted: a 10.x version must not
560
+ * reach the 11 gates, which a plain `gte` ladder would let it do.
561
+ */
562
+ function requestDestinationScalars(pnpmVersion) {
563
+ if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
564
+ if ((0, semver_1.lt)(pnpmVersion, '10.34.2')) {
565
+ return null;
566
+ }
567
+ return (0, semver_1.lt)(pnpmVersion, '10.34.5')
568
+ ? PNPM_REQUEST_DESTINATION_SCALARS_10
569
+ : PNPM_REQUEST_DESTINATION_SCALARS_10_34_5;
570
+ }
571
+ if ((0, semver_1.lt)(pnpmVersion, '11.5.3')) {
572
+ return null;
573
+ }
574
+ return (0, semver_1.lt)(pnpmVersion, '11.11.0')
575
+ ? PNPM_REQUEST_DESTINATION_SCALARS
576
+ : PNPM_REQUEST_DESTINATION_SCALARS_11_11;
577
+ }
578
+ /**
579
+ * A yaml settings file as pnpm's replaceEnvInSettings leaves it. Which
580
+ * `${VAR}` it touches moved twice, and what it does with one it cannot resolve
581
+ * moved once:
582
+ *
583
+ * - Keys, on every line from 10.7.0, and a key it resolves nothing for aborts
584
+ * the command. 10.6.0 has no replacer at all and takes the file verbatim.
585
+ * - Top-level string values, on the same line, and likewise fatal.
586
+ * - `registries` and `namedRegistries` values, from 11.1.0.
587
+ * - A scalar naming a request destination is dropped instead of expanded, from
588
+ * 10.34.2 on the 10 line and 11.5.3 on the 11 one
589
+ * (requestDestinationScalars). A `registries` entry holding one is dropped
590
+ * too, but from 11.5.3 alone: 10.x has no branch for that key and passes the
591
+ * map through whole. Both apply to a file a project controls; the global
592
+ * config.yaml is trusted and keeps expanding, and only 11 reads it anyway.
593
+ *
594
+ * A nested object elsewhere is passed through untouched on every line, so a
595
+ * placeholder there is neither expanded nor fatal.
596
+ *
597
+ * Values come back in the form npm's own expansion turns back into what pnpm
598
+ * resolved: a line that expands leaves an escaped reference for npm to consume
599
+ * (expandPnpmEnvVars), and a line that does not escapes what it passes through.
600
+ */
601
+ function resolveYamlEnv(doc, path, pnpmVersion, trusted) {
602
+ const assertResolves = (value) => {
603
+ const unresolved = (0, utils_1.unresolvedPnpmEnvVars)(value);
604
+ if (unresolved.length > 0) {
605
+ // pnpm aborts the command here, so there is no resolution left to
606
+ // reproduce. Propagating to the caller's fall-open warns instead. Only the
607
+ // references are named: the rest of the value can be a credential.
608
+ throw new Error(`The pnpm configuration file at ${path} references an environment variable that is not set: ${unresolved.join(', ')}`);
609
+ }
610
+ };
611
+ /** For a key, which is read rather than handed to npm. */
612
+ const expandKey = (value) => {
613
+ assertResolves(value);
614
+ return (0, utils_1.expandPnpmEnvVars)(value);
615
+ };
616
+ /** For a value, which the spawned npm expands again. */
617
+ const expandValue = (value) => {
618
+ assertResolves(value);
619
+ return (0, utils_1.bridgePnpmEnvVars)(value);
620
+ };
621
+ const expands = (0, semver_1.gte)(pnpmVersion, '10.7.0');
622
+ const resolveScalar = expands ? expandValue : utils_1.escapeNpmEnvExpr;
623
+ const resolveRegistry = (0, semver_1.gte)(pnpmVersion, '11.1.0')
624
+ ? resolveScalar
625
+ : utils_1.escapeNpmEnvExpr;
626
+ const droppedScalars = trusted
627
+ ? null
628
+ : requestDestinationScalars(pnpmVersion);
629
+ const dropsRegistries = !trusted && (0, semver_1.gte)(pnpmVersion, '11.5.3');
630
+ const resolved = {};
631
+ for (const [rawKey, value] of Object.entries(doc)) {
632
+ const key = expands ? expandKey(rawKey) : rawKey;
633
+ if (typeof value === 'string') {
634
+ if (droppedScalars?.has(key) && PNPM_ENV_PLACEHOLDER.test(value)) {
635
+ continue;
636
+ }
637
+ resolved[key] = resolveScalar(value);
638
+ }
639
+ else if (key === 'registries' || key === 'namedRegistries') {
640
+ resolved[key] = mapYamlStrings(value, (entry) => dropsRegistries && PNPM_ENV_PLACEHOLDER.test(entry)
641
+ ? undefined
642
+ : resolveRegistry(entry));
643
+ }
644
+ else {
645
+ resolved[key] = value;
646
+ }
647
+ }
648
+ return resolved;
649
+ }
650
+ /**
651
+ * `map` over the string values of a plain object, an entry it returns nothing
652
+ * for dropped. Anything else is passed through, which is how pnpm's own two
653
+ * mappers treat a shape they were not given.
654
+ */
655
+ function mapYamlStrings(value, map) {
656
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
657
+ return value;
658
+ }
659
+ const mapped = {};
660
+ for (const [key, entry] of Object.entries(value)) {
661
+ if (typeof entry !== 'string') {
662
+ mapped[key] = entry;
663
+ continue;
664
+ }
665
+ const result = map(entry);
666
+ if (result !== undefined) {
667
+ mapped[key] = result;
668
+ }
669
+ }
670
+ return mapped;
671
+ }
672
+ /**
673
+ * pnpm type-checks none of these, so its tolerance is uneven and each shape
674
+ * here mirrors a measured 11.10.0 outcome. A truthy non-string proxy breaks
675
+ * pnpm's own fetch, so it is fatal into the caller's fall-open; a falsy one is
676
+ * never read as a proxy at all and drops here the way it drops there. A
677
+ * wrong-shaped noProxy also survives pnpm, so it is dropped
678
+ * rather than handed to the string-typed spawn env. `registries` and
679
+ * `strictSsl` stay unnarrowed for the consumer that reads them, because pnpm
680
+ * only reacts to the registry value it picks, and turns TLS verification off
681
+ * for the boolean alone.
682
+ */
683
+ function normalizePnpmWorkspaceSettings(doc, path) {
684
+ const fail = (what) => {
685
+ throw new Error(`The pnpm configuration file at ${path} declares ${what}.`);
686
+ };
687
+ if (doc.proxy && typeof doc.proxy !== 'string') {
688
+ fail('a proxy that is not a string');
689
+ }
690
+ if (doc.httpProxy && typeof doc.httpProxy !== 'string') {
691
+ fail('an httpProxy that is not a string');
692
+ }
693
+ if (doc.httpsProxy && typeof doc.httpsProxy !== 'string') {
694
+ fail('an httpsProxy that is not a string');
695
+ }
696
+ const text = (key) => typeof doc[key] === 'string' ? doc[key] : undefined;
697
+ return {
698
+ registries: doc.registries,
699
+ strictSsl: doc.strictSsl,
700
+ registry: text('registry'),
701
+ proxy: text('proxy'),
702
+ httpProxy: text('httpProxy'),
703
+ httpsProxy: text('httpsProxy'),
704
+ noProxy: text('noProxy'),
705
+ noproxy: text('noproxy'),
706
+ ca: text('ca'),
707
+ cert: text('cert'),
708
+ key: text('key'),
709
+ };
710
+ }
711
+ /**
712
+ * The yaml registry for `key`, fatal when it exists with a non-string shape:
713
+ * pnpm dies in `new URL` on the registry it picks (measured on 11.10.0), and an
714
+ * entry it never picks harms nothing, so the check runs per pick rather than
715
+ * over the whole map. A registries that is not a map declares no entry at all,
716
+ * which is how pnpm resolves one.
717
+ */
718
+ function pickYamlRegistry(settings, key, path) {
719
+ const { registries } = settings;
720
+ if (registries === null ||
721
+ typeof registries !== 'object' ||
722
+ Array.isArray(registries)) {
723
+ return undefined;
724
+ }
725
+ const value = registries[key];
726
+ if (value === undefined) {
727
+ return undefined;
728
+ }
729
+ if (typeof value !== 'string') {
730
+ throw new Error(`The pnpm configuration file at ${path} declares a registries["${key}"] that is not a string.`);
731
+ }
732
+ return value;
733
+ }
734
+ function getAuthIniPath() {
735
+ return (0, path_1.join)((0, pnpm_config_1.getPnpmConfigDir)(process.env), 'auth.ini');
736
+ }
737
+ function getGlobalConfigPath() {
738
+ return (0, path_1.join)((0, pnpm_config_1.getPnpmConfigDir)(process.env), 'config.yaml');
739
+ }
740
+ /**
741
+ * The global config.yaml, null when absent. pnpm reads this one straight,
742
+ * without the existence check it puts in front of pnpm-workspace.yaml, so every
743
+ * command aborts on a file it cannot open or parse. That propagates to the
744
+ * caller's fall-open instead of resolving on without the file's settings.
745
+ */
746
+ function readPnpmGlobalConfigYaml() {
747
+ const path = getGlobalConfigPath();
748
+ const doc = (0, pnpm_config_1.readPnpmYamlConfig)(path);
749
+ if (doc === 'unusable') {
750
+ throw new Error(`The pnpm global configuration file at ${path} could not be read.`);
751
+ }
752
+ return doc;
753
+ }
754
+ /**
755
+ * The settings pnpm >= 11 takes from that file. It applies them the way it
756
+ * applies a workspace manifest, over the npmrc-derived config and under the
757
+ * workspace file's own, but only for the keys it allows there: `registries` is
758
+ * refused with a warning until 11.11.0, while every other key read here is an
759
+ * npm setting name it has always allowed. The file is the user's own rather
760
+ * than a project's, so a `${VAR}` naming a request destination is expanded
761
+ * instead of withheld.
762
+ */
763
+ function readPnpmGlobalSettings(pnpmVersion) {
764
+ if ((0, semver_1.lt)(pnpmVersion, '11.0.0')) {
765
+ return {};
766
+ }
767
+ const doc = readPnpmGlobalConfigYaml();
768
+ if (doc === null) {
769
+ return {};
770
+ }
771
+ const path = getGlobalConfigPath();
772
+ const settings = normalizePnpmWorkspaceSettings(resolveYamlEnv(doc, path, pnpmVersion, true), path);
773
+ if ((0, semver_1.lt)(pnpmVersion, '11.11.0')) {
774
+ delete settings.registries;
775
+ }
776
+ return settings;
777
+ }
778
+ // pnpm keeps resolving from the remaining layers for an npmrc-family file it
779
+ // cannot read, so mirror the absent semantics. It stays silent on ENOENT and
780
+ // EISDIR and warns otherwise; we warn for the whole unreadable class, minus the
781
+ // ENOTDIR the reader already reports as absent, where pnpm warns and resolves
782
+ // on all the same.
783
+ const warnedUnreadableFiles = new Set();
784
+ function warnUnreadableFile(path) {
785
+ if (warnedUnreadableFiles.has(path)) {
786
+ return;
787
+ }
788
+ warnedUnreadableFiles.add(path);
789
+ logger_1.logger.warn(`Could not read ${path}; resolving the pnpm registry configuration without it, the way pnpm itself does.`);
790
+ }
791
+ function readNpmrcEntriesOrWarn(path) {
792
+ const entries = (0, npmrc_1.readNpmrcEntries)(path);
793
+ if (entries !== 'unreadable') {
794
+ return entries;
795
+ }
796
+ warnUnreadableFile(path);
797
+ return null;
798
+ }
799
+ /** An npmrc-family file as written, null when it could not be read. */
800
+ function readNpmrcOrWarn(path) {
801
+ const entries = readNpmrcEntriesOrWarn(path);
802
+ return entries && (0, npmrc_1.npmrcEntriesToMap)(entries);
803
+ }
804
+ /**
805
+ * The same file as pnpm ends up with it, which below 11 can be not at all: its
806
+ * reader expands `${VAR}` in both halves of an entry through a function that
807
+ * throws on a reference it resolves nothing for, and the config chain catches
808
+ * that per file, so one bad reference costs every entry in the file and pnpm
809
+ * carries on from the layers below. From 11 the lossy reader substitutes an
810
+ * empty string per entry instead and the file survives.
811
+ * See parseKey/parseField and Conf.addFile in pnpm's bundled npm-conf.
812
+ */
813
+ function readPnpmNpmrcMap(path, pnpmVersion) {
814
+ const entries = readNpmrcEntriesOrWarn(path);
815
+ if (entries === null || (0, semver_1.gte)(pnpmVersion, '11.0.0')) {
816
+ return entries && (0, npmrc_1.npmrcEntriesToMap)(entries);
817
+ }
818
+ // parseField hands a `key[]` array straight back, so the values under a key
819
+ // ini collected into one are never expanded and never throw. The key is, and
820
+ // one repeated line is enough to make every value under it an array.
821
+ const arrayKeys = new Set(entries.filter((entry) => entry.array).map((entry) => entry.key));
822
+ for (const { key, value } of entries) {
823
+ if (!(0, utils_1.pnpmEnvVarsResolve)(key) ||
824
+ (!arrayKeys.has(key) && !(0, utils_1.pnpmEnvVarsResolve)(value))) {
825
+ return null;
826
+ }
827
+ }
828
+ return (0, npmrc_1.npmrcEntriesToMap)(entries);
829
+ }
830
+ // pnpm's AUTH_VALUE_KEYS, which holds the same seven settings as its
831
+ // UNSCOPED_RESCOPABLE_KEYS. Aliased rather than spelled out again so the two
832
+ // cannot drift apart here while pnpm keeps them equal.
833
+ const PNPM_AUTH_VALUE_KEYS = PNPM_RESCOPABLE_KEYS;
834
+ // pnpm's hasEnvPlaceholder, which unlike its expander honors no escape.
835
+ const PNPM_ENV_PLACEHOLDER = /\$\{[^}]+\}/;
836
+ const isRegistryKey = (key) => key === 'registry' || (key.startsWith('@') && key.endsWith(':registry'));
837
+ const isAuthValueKey = (key) => PNPM_AUTH_VALUE_KEYS.some((k) => key === k || key.endsWith(`:${k}`));
838
+ /** pnpm's isRequestDestinationKey: the set its key-side test covers. */
839
+ const isRequestDestinationKey = (key) => isRegistryKey(key) || key.startsWith('//');
840
+ /** pnpm's isRequestDestinationValueKey: the set its value-side test covers. */
841
+ const isRequestDestinationValueKey = (key) => isRegistryKey(key) ||
842
+ key === 'proxy' ||
843
+ key === 'http-proxy' ||
844
+ key === 'https-proxy';
845
+ /** Whether 11.5.3+ drops an entry rather than expanding its `${VAR}`. */
846
+ function pnpmDropsProjectEntry(rawKey, key, rawValue) {
847
+ if (PNPM_ENV_PLACEHOLDER.test(rawKey) &&
848
+ // Tested on both sides of the expansion, because a placeholder can carry a
849
+ // key that is plain in neither set into one of them and vice versa.
850
+ (isRequestDestinationKey(rawKey) ||
851
+ isAuthValueKey(rawKey) ||
852
+ isRequestDestinationKey(key) ||
853
+ isAuthValueKey(key))) {
854
+ return true;
855
+ }
856
+ return (PNPM_ENV_PLACEHOLDER.test(rawValue) &&
857
+ (isRequestDestinationValueKey(key) || isAuthValueKey(key)));
858
+ }
859
+ /**
860
+ * One npmrc-family file as pnpm reads it: both halves expanded, rebuilt in file
861
+ * order so a later key that resolves to the same setting wins, the way pnpm's
862
+ * own assignment does. npm's env-tier expansion uses a different grammar, which
863
+ * is why the values are expanded here rather than left to the spawn.
864
+ *
865
+ * `filtered` applies the rule pnpm puts on the workspace file alone: until
866
+ * 11.5.3 a `${VAR}` expanded there like anywhere else, so a placeholder-keyed
867
+ * entry lands under the spelling it expands to; from 11.5.3 an entry naming a
868
+ * host or carrying a credential is dropped instead of expanded when either half
869
+ * holds one. See readAndFilterNpmrc in pnpm's config reader.
870
+ */
871
+ function readPnpmNpmrcEntries(raw, pnpmVersion, filtered) {
872
+ const drops = filtered && (0, semver_1.gte)(pnpmVersion, '11.5.3');
873
+ const map = new Map();
874
+ for (const [rawKey, rawValue] of raw) {
875
+ const key = PNPM_ENV_PLACEHOLDER.test(rawKey)
876
+ ? (0, utils_1.expandPnpmEnvVars)(rawKey)
877
+ : rawKey;
878
+ if (drops && pnpmDropsProjectEntry(rawKey, key, rawValue)) {
879
+ continue;
880
+ }
881
+ map.set(key, rawValue);
882
+ }
883
+ // parseField decides a Boolean-typed setting from the literal value, before it
884
+ // expands any `${VAR}`, so strict-ssl has to be read pre-expansion.
885
+ const rawStrictSsl = map.get('strict-ssl');
886
+ // What pnpm itself resolves this file's own registry to. The escaped form
887
+ // below is text for npm to expand, not a URL: `\` is a path separator to the
888
+ // URL parser, so darting it would key the credential a segment off.
889
+ const fileRegistry = (0, utils_1.expandPnpmEnvVars)(map.get('registry') ?? '');
890
+ for (const [key, value] of map) {
891
+ // cafile is joined onto a directory before it is handed over, and on Windows
892
+ // the backslashes an escape adds are separators that path normalization
893
+ // collapses, leaving npm to consume one of them as the escape. It is escaped
894
+ // once resolved instead.
895
+ map.set(key, key === 'cafile' ? (0, utils_1.expandPnpmEnvVars)(value) : (0, utils_1.bridgePnpmEnvVars)(value));
896
+ }
897
+ // pnpm's getDefaultCreds applies a bare global _authToken/_auth/username/
898
+ // _password (no nerf-dart prefix); npm honors auth only in the nerf-darted
899
+ // form, so re-key each onto the registry this file itself carries, or npmjs
900
+ // when it carries none. pnpm does this per file and before the merge
901
+ // (rescopeUnscopedCreds), which is why two files can each contribute a bare
902
+ // credential under a different dart, and why a dart the same file spells out
903
+ // keeps its own value. From 11.4.0 that is pnpm's own pin; earlier 11.x paired
904
+ // the credential with whichever registry won overall, which let a
905
+ // workspace-local .npmrc or pnpm-workspace.yaml aim a user-level credential at
906
+ // a host of its choosing (CVE-2026-50017), so the pin is applied there too
907
+ // rather than reproducing the hole.
908
+ const dart = pnpmNerfDart(fileRegistry || DEFAULT_REGISTRY, pnpmVersion);
909
+ const rescoped = [];
910
+ for (const bareKey of PNPM_RESCOPABLE_KEYS) {
911
+ const value = map.get(bareKey);
912
+ if (value === undefined) {
913
+ continue;
914
+ }
915
+ map.delete(bareKey);
916
+ // An unparseable registry leaves pnpm nowhere safe to pin them, so it drops
917
+ // them outright.
918
+ if (!dart) {
919
+ continue;
920
+ }
921
+ // Re-keyed on presence, an empty value included, because that is how pnpm
922
+ // does it: an emptied credential goes on to shadow the same key in every
923
+ // file below, which is how a workspace clears one it inherits.
924
+ if (!map.has(`${dart}:${bareKey}`)) {
925
+ map.set(`${dart}:${bareKey}`, value);
926
+ }
927
+ // Nothing is withheld from npm when the credential was empty to begin with.
928
+ if (value && BARE_AUTH_KEYS.includes(bareKey)) {
929
+ rescoped.push(bareKey);
930
+ }
931
+ }
932
+ return { map, rawStrictSsl, rescoped, dart };
933
+ }
934
+ /**
935
+ * The .npmrc files pnpm reads below its own environment, highest first: the one
936
+ * beside the package.json the command runs from, then the one beside the
937
+ * workspace manifest it walked up to. They are the same file for a workspace
938
+ * that is its own root, and the second is the tier npm has none of.
939
+ */
940
+ function pnpmNpmrcPaths(root, workspaceFile) {
941
+ const project = (0, path_1.join)(root, '.npmrc');
942
+ const workspaceDir = workspaceFile ? (0, path_1.dirname)(workspaceFile) : root;
943
+ return workspaceDir === root
944
+ ? [project]
945
+ : [project, (0, path_1.join)(workspaceDir, '.npmrc')];
946
+ }
947
+ /**
948
+ * An npmrc-family file with its keys expanded, rebuilt in file order so a later
949
+ * key that expands onto the same setting wins the way pnpm's own assignment
950
+ * does. Values stay as written, because parseField types a Boolean setting from
951
+ * the literal before any `${VAR}` in it is expanded.
952
+ */
953
+ function expandPnpmNpmrcKeys(raw) {
954
+ const map = new Map();
955
+ for (const [rawKey, rawValue] of raw) {
956
+ map.set((0, utils_1.expandPnpmEnvVars)(rawKey), rawValue);
957
+ }
958
+ return map;
959
+ }
960
+ /**
961
+ * A registry the yaml or the environment already forced in outranks these files
962
+ * in pnpm, so it keeps winning here.
963
+ */
964
+ function applyPnpmFileRegistry(env, scope, bridged) {
965
+ const registry = bridged('registry');
966
+ if (!env['npm_config_registry'] && registry) {
967
+ (0, utils_1.setRegistry)(env, registry);
968
+ }
969
+ const scopedRegistry = scope ? bridged(`${scope}:registry`) : undefined;
970
+ if (scope && !env[`npm_config_${scope}:registry`] && scopedRegistry) {
971
+ (0, utils_1.setScopedRegistry)(env, scope, scopedRegistry);
972
+ }
973
+ }
974
+ /**
975
+ * `raw` is the value as written, before any `${VAR}` expansion: strict-ssl is
976
+ * typed Boolean-only, so parseField turns just 'true'/'false' (plus '' -> true
977
+ * and the null/undefined literals) into non-strings and leaves everything else
978
+ * a truthy string. '0', 'no' and 'off' all keep TLS verification on in pnpm;
979
+ * only an explicit 'false' turns it off.
980
+ */
981
+ function applyPnpmStrictSsl(env, raw) {
982
+ if (raw !== undefined) {
983
+ (0, utils_1.setStrictSsl)(env, raw !== 'false');
984
+ }
985
+ }
986
+ /**
987
+ * The workspace .npmrc pnpm below 11 layers under the project one. npm has no
988
+ * tier for it at all, so a setting the project file leaves undeclared has to
989
+ * reach npm through the environment; one the project file declares npm resolves
990
+ * for itself, and injecting the workspace value would put it above that file
991
+ * rather than below it. The ambient npm_config_* both readers honor on this line
992
+ * outranks either file, so a setting declared there is left alone as well.
993
+ *
994
+ * Nothing is bridged out of `userConfigPath`, which npm reads for itself, but
995
+ * the proxies pnpm derives from one another are resolved across it: a tier
996
+ * missing from the lookup makes an `https-proxy` it declares read as
997
+ * undeclared, and the caller then derives one from a legacy `proxy` a file
998
+ * above it set.
999
+ *
1000
+ * A bare credential is deliberately not bridged. pnpm has no per-file rescoping
1001
+ * here and pins one to nerfDart(allSettings.registry), the registry the npmrc
1002
+ * chain resolves rather than the one the pnpm-workspace.yaml sends the fetch to,
1003
+ * so npm's nerf-darted form cannot be derived from what this can see.
1004
+ */
1005
+ function bridgeWorkspaceNpmrc(env, npmrcPaths, userConfigPath, scope, pnpmVersion) {
1006
+ const [projectPath, workspacePath] = npmrcPaths;
1007
+ // pnpm's view of the shadowing tier: a file its reader discarded shadows
1008
+ // nothing, even though npm goes on reading that same file for itself.
1009
+ const tiers = [...npmrcPaths, userConfigPath].map((path) => {
1010
+ const raw = readPnpmNpmrcMap(path, pnpmVersion);
1011
+ return raw && expandPnpmNpmrcKeys(raw);
1012
+ });
1013
+ const projectNpmrc = tiers[0];
1014
+ const workspaceNpmrc = workspacePath ? tiers[1] : null;
1015
+ /** What pnpm resolves from these files and the env tier over them. */
1016
+ const resolved = (key) => (0, utils_1.bridgePnpmEnvVars)((0, utils_1.readNpmConfigEnv)(process.env, key) ??
1017
+ tiers.find((tier) => tier?.has(key))?.get(key) ??
1018
+ '') || undefined;
1019
+ const proxies = {
1020
+ proxy: resolved('proxy'),
1021
+ httpProxy: resolved('http-proxy'),
1022
+ httpsProxy: resolved('https-proxy'),
1023
+ };
1024
+ if (!workspaceNpmrc) {
1025
+ return proxies;
1026
+ }
1027
+ /** The value as written, unless a tier above the workspace file declares one. */
1028
+ const declared = (key) => projectNpmrc?.has(key) || (0, utils_1.readNpmConfigEnv)(process.env, key) !== undefined
1029
+ ? undefined
1030
+ : workspaceNpmrc.get(key);
1031
+ // An empty value declares nothing to derive from: pnpm's own readers re-check
1032
+ // for an empty registry, and npm skips an empty env value outright. Deriving
1033
+ // from one is what does damage (an empty cafile resolves to its own directory).
1034
+ const bridged = (key) => (0, utils_1.bridgePnpmEnvVars)(declared(key) ?? '') || undefined;
1035
+ applyPnpmFileRegistry(env, scope, bridged);
1036
+ // Every dart is copied, not just the contacted registry's: npm resolves auth
1037
+ // per fetched URI and sends only the matching key, so a tarball served from a
1038
+ // second authenticated host keeps working. Filtering here would strip it.
1039
+ for (const key of workspaceNpmrc.keys()) {
1040
+ // npm has no tokenHelper setting, and pnpm takes one from its user config
1041
+ // alone, so a scoped helper here stands for no credential the fetch had.
1042
+ // `:cert`/`:key` carry inline PEM, which neither tool reads in scoped form
1043
+ // (pnpm's getNetworkConfigs pairs a registry with `:certfile`/`:keyfile`
1044
+ // paths, the same keys npm resolves per URI, and those do go through).
1045
+ if (!key.startsWith('//') ||
1046
+ key.endsWith(':tokenHelper') ||
1047
+ key.endsWith(':cert') ||
1048
+ key.endsWith(':key')) {
1049
+ continue;
1050
+ }
1051
+ const value = bridged(key);
1052
+ if (value) {
1053
+ env[`npm_config_${key}`] = value;
1054
+ }
1055
+ }
1056
+ // Resolved before the npm-facing escape: on Windows the backslashes it adds
1057
+ // are separators, which normalization collapses and npm then reads one of them
1058
+ // as the escape.
1059
+ const cafile = (0, utils_1.expandPnpmEnvVars)(declared('cafile') ?? '') || undefined;
1060
+ if (cafile) {
1061
+ // pnpm's only reader on this line is loadCAFile, a bare readFileSync on the
1062
+ // raw value, so a relative one resolves against the cwd the command runs in,
1063
+ // which is the root the spawn uses. It expands no leading `~`, and npm
1064
+ // ignores a cafile it cannot open, so getting the base wrong drops the trust
1065
+ // anchor with no diagnostic at all. (11.2.0 moved that base to the directory
1066
+ // of the declaring file.)
1067
+ (0, utils_1.setCafile)(env, (0, utils_1.escapeNpmEnvExpr)((0, path_1.resolve)((0, path_1.dirname)(projectPath), cafile)));
1068
+ }
1069
+ // Flat keys on this line: pnpm pins neither trust anchors nor client TLS
1070
+ // material to a registry before 11, and npm reads all three the same way.
1071
+ for (const key of ['ca', 'cert', 'key']) {
1072
+ const value = bridged(key);
1073
+ if (value) {
1074
+ env[`npm_config_${key}`] = value;
1075
+ }
1076
+ }
1077
+ applyPnpmStrictSsl(env, declared('strict-ssl'));
1078
+ (0, utils_1.setProxies)(env, {
1079
+ // The spelling npm reads natively, which it can still only read from its own
1080
+ // project config. pnpm prefers `no-proxy` across every layer over `noproxy`
1081
+ // across every layer, so bridgeNoProxy runs after this and overwrites it.
1082
+ noProxy: bridged('noproxy'),
1083
+ });
1084
+ return proxies;
1085
+ }
1086
+ function bridgeNpmrcSources(env, root, workspaceDir, scope, authIniPath, userConfigPath, pnpmVersion, managerIgnoresEnv) {
1087
+ // The file npm resolves as its project config, beside the package.json the
1088
+ // spawn runs from.
1089
+ const projectRaw = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc'));
1090
+ // pnpm reads exactly one workspace .npmrc, beside the workspace manifest it
1091
+ // walked up to, and merges it over auth.ini. That file is npm's own only when
1092
+ // the two directories coincide; above the spawn's, it is a source only pnpm
1093
+ // reads, so its entries have to be bridged rather than left to npm.
1094
+ const workspaceRaw = workspaceDir === root
1095
+ ? projectRaw
1096
+ : readNpmrcOrWarn((0, path_1.join)(workspaceDir, '.npmrc'));
1097
+ const authIniRaw = readNpmrcOrWarn(authIniPath);
1098
+ const trustedWorkspace = isTrustedWorkspaceNpmrc(workspaceDir, userConfigPath);
1099
+ // The same file at the workspace tier already, where pnpm trusts and reads it.
1100
+ const userRaw = trustedWorkspace ? null : readNpmrcOrWarn(userConfigPath);
1101
+ // Highest pnpm precedence first, matching the order it assigns them in
1102
+ // (workspace .npmrc over auth.ini over the file it authenticates from).
1103
+ const sources = [];
1104
+ if (workspaceRaw) {
1105
+ sources.push({
1106
+ dir: workspaceDir,
1107
+ npmNative: workspaceDir === root,
1108
+ ...readPnpmNpmrcEntries(workspaceRaw, pnpmVersion, !trustedWorkspace),
1109
+ });
1110
+ }
1111
+ if (authIniRaw) {
1112
+ sources.push({
1113
+ dir: (0, path_1.dirname)(authIniPath),
1114
+ npmNative: false,
1115
+ ...readPnpmNpmrcEntries(authIniRaw, pnpmVersion, false),
1116
+ });
1117
+ }
1118
+ if (userRaw) {
1119
+ sources.push({
1120
+ dir: (0, path_1.dirname)(userConfigPath),
1121
+ // pnpm authenticates from the file `npmrcAuthFile`/`userconfig` selects.
1122
+ // npm opens that same file only where its own `userconfig` lands on it;
1123
+ // anywhere else it is a source npm never reads, so its entries need
1124
+ // bridging like auth.ini's.
1125
+ npmNative: userConfigPath === getNpmUserConfigPath(root),
1126
+ ...readPnpmNpmrcEntries(userRaw, pnpmVersion, false),
1127
+ });
1128
+ }
1129
+ if (sources.length === 0) {
1130
+ return {};
1131
+ }
1132
+ const projectNpmrc = projectRaw ?? new Map();
1133
+ /** The highest source declaring `key`; an empty value still shadows the rest. */
1134
+ const declaringSource = (key) => sources.find((source) => source.map.has(key));
1135
+ /**
1136
+ * What pnpm resolves from these files, npm-native or not: a value it derives
1137
+ * another setting from is one npm does not derive for itself.
1138
+ */
1139
+ const declaredValue = (key) => declaringSource(key)?.map.get(key) || undefined;
1140
+ /** That source, unless npm reads it for itself and needs no bridging. */
1141
+ const bridging = (key) => {
1142
+ const source = declaringSource(key);
1143
+ return source?.npmNative === false ? source : undefined;
1144
+ };
1145
+ // An empty value declares nothing to derive from: pnpm's own readers re-check
1146
+ // for an empty registry, and npm skips an empty env value outright. Deriving
1147
+ // from one is what does damage (an empty cafile resolves to its own directory).
1148
+ const bridgedValue = (key) => bridging(key)?.map.get(key) || undefined;
1149
+ applyPnpmFileRegistry(env, scope, bridgedValue);
1150
+ // Every dart is copied, not just the contacted registry's: npm resolves auth
1151
+ // per fetched URI and sends only the matching key, so a tarball served from a
1152
+ // second authenticated host keeps working. Filtering here would strip it.
1153
+ for (const source of sources) {
1154
+ for (const [key, value] of source.map) {
1155
+ // A key a higher source declares is that source's to decide, whether it
1156
+ // bridges the value or leaves it to npm.
1157
+ if (!key.startsWith('//') || declaringSource(key) !== source) {
1158
+ continue;
1159
+ }
1160
+ // npm has no tokenHelper setting and pnpm ignores one that arrives through
1161
+ // the environment, so bridging it would only put a command line in the
1162
+ // child's environment. pnpm also refuses to run a helper from any file but
1163
+ // its user auth config, so one here stands for no credential the fetch
1164
+ // would have had. `:cert`/`:key` carry inline PEM, which npm has no
1165
+ // registry-scoped form for, so they go in flat below instead.
1166
+ if (source.npmNative ||
1167
+ key.endsWith(':tokenHelper') ||
1168
+ key.endsWith(':cert') ||
1169
+ key.endsWith(':key')) {
1170
+ continue;
1171
+ }
1172
+ // The env checks keep the URL-scoped env tier above these files, matching
1173
+ // pnpm's merge order: pnpm_config_ spellings are already in the overlay
1174
+ // (applyUrlScopedEnvConfig); ambient npm_config_ ones pnpm reads must stay
1175
+ // unbridged, or the overlaid value would shadow them out of the merge.
1176
+ if (env[`npm_config_${key}`] !== undefined ||
1177
+ (!managerIgnoresEnv(key) &&
1178
+ (0, utils_1.readNpmConfigEnv)(process.env, key) !== undefined)) {
1179
+ continue;
1180
+ }
1181
+ env[`npm_config_${key}`] = value;
1182
+ }
1183
+ }
1184
+ // The rescoped credentials went in through the dart loop above, since that is
1185
+ // the form they carry by the time pnpm merges them. What is left is naming the
1186
+ // ones npm will not get: a file npm reads itself contributes none, because npm
1187
+ // rejects bare auth in its own config (ERR_INVALID_AUTH) before any overlay
1188
+ // matters.
1189
+ const bareKeys = new Set();
1190
+ const credentialDarts = new Set();
1191
+ for (const source of sources) {
1192
+ if (source.npmNative || !source.dart) {
1193
+ continue;
1194
+ }
1195
+ for (const key of source.rescoped) {
1196
+ bareKeys.add(key);
1197
+ credentialDarts.add(source.dart);
1198
+ }
1199
+ }
1200
+ const requestDart = (0, utils_1.requestNerfDart)(contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv));
1201
+ const requestKeys = requestDart ? (0, utils_1.registryKeysFor)(requestDart) : [];
1202
+ // A withheld credential is invisible in npm's own error, so name it, unless
1203
+ // npm already finds one for that registry among the sources visible here. A
1204
+ // user-level ~/.npmrc is not one, so the message states only what was
1205
+ // withheld rather than predicting how the request will fail.
1206
+ if (bareKeys.size > 0 &&
1207
+ requestDart &&
1208
+ !requestKeys.some((key) => credentialDarts.has(key)) &&
1209
+ !hasCredentials(env, projectNpmrc, requestDart, managerIgnoresEnv)) {
1210
+ warnUnscopedCredential(requestDart, [...bareKeys]);
1211
+ }
1212
+ // Flat TLS/proxy keys are part of pnpm's auth-config inheritance set
1213
+ // (RAW_AUTH_CFG_KEYS) and are written to auth.ini by `pnpm config set`, so
1214
+ // bridge them too.
1215
+ const cafileSource = bridging('cafile');
1216
+ const cafile = bridgedValue('cafile');
1217
+ if (cafileSource && cafile) {
1218
+ // From 11.2.0 pnpm resolves a relative cafile against the directory of the
1219
+ // file that declared it, not the workspace root; before that its only reader
1220
+ // is loadCAFile, a bare readFileSync on the raw value, so it lands on the
1221
+ // cwd the command runs in (the workspace root for a migrate). Neither
1222
+ // expands a leading `~`. npm ignores a cafile it cannot open, so getting the
1223
+ // base wrong drops the trust anchor with no diagnostic at all.
1224
+ const base = (0, semver_1.gte)(pnpmVersion, '11.2.0') ? cafileSource.dir : root;
1225
+ (0, utils_1.setCafile)(env, (0, utils_1.escapeNpmEnvExpr)((0, path_1.resolve)(base, cafile)));
1226
+ }
1227
+ // npm reads inline `ca` PEM only as a flat (global) key, and pnpm does not
1228
+ // source-scope trust anchors, so it needs no pin check.
1229
+ const ca = bridgedValue('ca');
1230
+ if (ca) {
1231
+ env['npm_config_ca'] = ca;
1232
+ }
1233
+ // `cert`/`key` are client TLS material, which pnpm pins to a registry the same
1234
+ // way it pins credentials, so by here they are darted. npm has no
1235
+ // registry-scoped inline form (its //host/:certfile / :keyfile keys take
1236
+ // paths, not PEM) and npm_config_cert presents the certificate to every host
1237
+ // npm contacts, so only the pair pinned to the registry npm will actually
1238
+ // contact can go in, and it goes in flat.
1239
+ // Every tier is read here, the URL-scoped env one above the files and the
1240
+ // project file among them: npm's own registry-scoped TLS keys take paths
1241
+ // (certfile/keyfile), so inline PEM cannot reach it in scoped form from any of
1242
+ // them, npm-native file included. Which tier declares it settles the value
1243
+ // first and the nearest dart declaring one then wins, the order pnpm resolves
1244
+ // them in (pickSettingByUrl walks a map every tier has already merged into).
1245
+ const pinnedTls = (dartKey) => env[`npm_config_${dartKey}`] ||
1246
+ // The same ambient tier the dart loop honors: from 11.6.0 pnpm reads a
1247
+ // URL-scoped npm_config_ entry the spawn would otherwise pass straight
1248
+ // through in a form npm makes no use of.
1249
+ (managerIgnoresEnv(dartKey)
1250
+ ? undefined
1251
+ : (0, utils_1.readNpmConfigEnv)(process.env, dartKey)) ||
1252
+ declaringSource(dartKey)?.map.get(dartKey);
1253
+ if (requestDart) {
1254
+ for (const key of ['cert', 'key']) {
1255
+ const value = requestKeys
1256
+ .map((regKey) => pinnedTls(`${regKey}:${key}`))
1257
+ .find(Boolean);
1258
+ if (value) {
1259
+ env[`npm_config_${key}`] = value;
1260
+ }
1261
+ else if (projectRaw &&
1262
+ // Read as npm resolves it: it expands a `${VAR}` in the key before it
1263
+ // looks the setting up, so a placeholder-spelled one still reaches it.
1264
+ (0, utils_1.readExpandedKey)(projectRaw, key, utils_1.expandNpmEnvVars) !== undefined) {
1265
+ // npm reads this one out of its own project config and presents it to
1266
+ // every host it contacts, where pnpm pinned it to a registry this fetch
1267
+ // never reaches. The `null` literal is what cancels a file value at npm's
1268
+ // env tier; an empty one leaves the file's in place (measured on npm 9,
1269
+ // 10 and 11).
1270
+ env[`npm_config_${key}`] = 'null';
1271
+ }
1272
+ }
1273
+ }
1274
+ applyPnpmStrictSsl(env, bridging('strict-ssl')?.rawStrictSsl);
1275
+ return {
1276
+ proxy: declaredValue('proxy'),
1277
+ httpProxy: declaredValue('http-proxy'),
1278
+ httpsProxy: declaredValue('https-proxy'),
1279
+ };
1280
+ }
1281
+ /**
1282
+ * The proxy-bypass list is the one npmrc key whose spelling differs. In these
1283
+ * files pnpm 11 honors `no-proxy` and ignores `noproxy`, where npm does the
1284
+ * exact opposite (it warns about `no-proxy` as an unknown config and moves on).
1285
+ * pnpm 10.x honors both, so only the spelling npm cannot read needs bridging on
1286
+ * either line. Either way pnpm's `no-proxy` never reaches the spawned npm from
1287
+ * any file it reads, so the layer that wins in pnpm has to be re-spelled. A
1288
+ * `noProxy` in pnpm-workspace.yaml outranks every one of them and is applied
1289
+ * after this.
1290
+ */
1291
+ function bridgeNoProxy(env, npmrcPaths, pnpmVersion) {
1292
+ const value = fileNoProxy(npmrcPaths, pnpmVersion);
1293
+ if (value) {
1294
+ (0, utils_1.setProxies)(env, { noProxy: value });
1295
+ }
1296
+ }
1297
+ /** The bypass list the highest of `npmrcPaths` to declare one contributes. */
1298
+ function fileNoProxy(npmrcPaths, pnpmVersion) {
1299
+ for (const path of npmrcPaths) {
1300
+ const npmrc = readPnpmNpmrcMap(path, pnpmVersion);
1301
+ // Declaring the key empty is pnpm's way of clearing a list it inherits, so
1302
+ // presence settles the layer and an empty value stops the search here.
1303
+ if (!npmrc?.has('no-proxy')) {
1304
+ continue;
1305
+ }
1306
+ const value = npmrc.get('no-proxy');
1307
+ // npm ignores `no-proxy` in the file it does read, so the value never goes
1308
+ // through npm's own expansion under that key; expand it with pnpm's grammar.
1309
+ return value ? (0, utils_1.bridgePnpmEnvVars)(value) : undefined;
1310
+ }
1311
+ return undefined;
1312
+ }
1313
+ /**
1314
+ * The registry the spawned npm will contact, as far as this process can see: a
1315
+ * scoped registry for the package, else the default, else npm's own. A registry
1316
+ * declared only in a user-level ~/.npmrc is not visible here, which leaves the
1317
+ * comparison covering the sources that can redirect the request to a host the
1318
+ * user never configured.
1319
+ */
1320
+ function contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv) {
1321
+ // npm's pickRegistry falls through on a falsy value, so a setting that
1322
+ // expanded to nothing lands on the next one rather than on an empty host.
1323
+ return ((scope
1324
+ ? npmResolved(env, projectNpmrc, `${scope}:registry`, managerIgnoresEnv)
1325
+ : undefined) ||
1326
+ npmResolved(env, projectNpmrc, 'registry', managerIgnoresEnv) ||
1327
+ DEFAULT_REGISTRY);
1328
+ }
1329
+ /**
1330
+ * Writes the proxy pair pnpm ends up with, once every tier has declared. The
1331
+ * registry npm is about to contact decides whether an http-only proxy is worth
1332
+ * bridging, and what npm reads for itself decides whether a value needs to be.
1333
+ */
1334
+ function applyResolvedProxies(env, tiers, root, scope, managerIgnoresEnv) {
1335
+ const projectNpmrc = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc')) ?? new Map();
1336
+ (0, utils_1.setProxies)(env, resolveProxies(tiers, contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv),
1337
+ // The user config counts as what npm sees: a proxy it holds there is one
1338
+ // npm resolves for itself, and overwriting it with a value derived from
1339
+ // another key would put the overlay above a file npm was already reading.
1340
+ npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv)));
1341
+ }
1342
+ function npmResolved(env, projectNpmrc, key, managerIgnoresEnv) {
1343
+ // npm's env tier outranks the .npmrc, but the spawn strips a bridged ambient
1344
+ // npm_config_* the manager ignores (mergeNpmConfigEnv), and every key read
1345
+ // here is bridged, so a value npm never sees is not counted either.
1346
+ const ambient = managerIgnoresEnv(key)
1347
+ ? undefined
1348
+ : (0, utils_1.readNpmConfigEnv)(process.env, key);
1349
+ const declared = env[`npm_config_${key}`] ??
1350
+ ambient ??
1351
+ (0, utils_1.readExpandedKey)(projectNpmrc, key, utils_1.expandNpmEnvVars);
1352
+ // npm trims a value before it expands one (parseField), so a blank value
1353
+ // collapses while a padded reference still resolves.
1354
+ return declared === undefined ? undefined : (0, utils_1.expandNpmEnvVars)(declared.trim());
1355
+ }
1356
+ /**
1357
+ * What the spawned npm resolves a key to on its own: its env tier, minus the
1358
+ * bridged spellings the spawn strips, then the two .npmrc files it opens here.
1359
+ * An unreadable one is silently absent, which is what npm makes of it.
1360
+ */
1361
+ function npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv) {
1362
+ const userConfig = (0, npmrc_1.readNpmrcMap)(getNpmUserConfigPath(root));
1363
+ return (key) => {
1364
+ const declared = npmResolved(env, projectNpmrc, key, managerIgnoresEnv);
1365
+ if (declared !== undefined || !(userConfig instanceof Map)) {
1366
+ return declared;
1367
+ }
1368
+ const value = (0, utils_1.readExpandedKey)(userConfig, key, utils_1.expandNpmEnvVars);
1369
+ return value === undefined ? undefined : (0, utils_1.expandNpmEnvVars)(value.trim());
1370
+ };
1371
+ }
1372
+ /**
1373
+ * What pnpm itself ends up with for a key, across the tiers the overlay already
1374
+ * carries and the npmrc-family files it reads for itself, each as its own
1375
+ * reader leaves it: a file it discarded whole declares nothing, and neither
1376
+ * does an entry it withheld. A key it answers is one npm answering the same way
1377
+ * reproduces rather than diverges from.
1378
+ */
1379
+ function pnpmResolvedReader(env, files, pnpmVersion, managerIgnoresEnv) {
1380
+ const maps = files.map(({ path, filtered }) => {
1381
+ const raw = readPnpmNpmrcMap(path, pnpmVersion);
1382
+ return raw && readPnpmNpmrcEntries(raw, pnpmVersion, filtered).map;
1383
+ });
1384
+ return (key) =>
1385
+ // The overlay is pnpm's own resolution of every tier above these files.
1386
+ env[`npm_config_${key}`] ||
1387
+ (managerIgnoresEnv(key) ? undefined : (0, utils_1.readNpmConfigEnv)(process.env, key)) ||
1388
+ maps.find((map) => map?.get(key))?.get(key);
1389
+ }
1390
+ function hasCredentials(env, projectNpmrc, dart, managerIgnoresEnv) {
1391
+ return (0, utils_1.hasCredentialFor)(dart, (key) => npmResolved(env, projectNpmrc, key, managerIgnoresEnv));
1392
+ }
1393
+ /** pnpm accepts `~/` and `~\` on every platform; npm accepts `~\` on Windows only. */
1394
+ const PNPM_HOME_PATH = /^~[/\\]/;
1395
+ const NPM_HOME_PATH = process.platform === 'win32' ? /^~[/\\]/ : /^~\//;
1396
+ /** Both tools normalize a config path this way: a leading `~/` (or `~\`) for
1397
+ * the home directory, else the cwd the command runs in. That cwd is the config
1398
+ * root the spawn uses, not this process's, which a migrate from a subdirectory
1399
+ * differs from. */
1400
+ function resolveConfigPath(value, homePattern, root) {
1401
+ return homePattern.test(value)
1402
+ ? (0, path_1.resolve)((0, os_1.homedir)(), value.slice(2))
1403
+ : (0, path_1.resolve)(root, value);
1404
+ }
1405
+ /**
1406
+ * The file pnpm >= 11 authenticates from. Its selection chain is followed here
1407
+ * minus the two CLI links, which nx never passes.
1408
+ * See loadNpmrcConfig in pnpm's config reader.
1409
+ */
1410
+ function getPnpmUserConfigPath(pnpmVersion, root) {
1411
+ // Read first: pnpm parses the global config.yaml before the selector
1412
+ // applies, so a malformed one aborts even when the env names the auth file.
1413
+ const globalYaml = readPnpmGlobalConfigYaml();
1414
+ let selected = readPnpmEnvVar('npmrc_auth_file', pnpmVersion) ??
1415
+ readPnpmEnvVar('userconfig', pnpmVersion);
1416
+ if (selected === undefined) {
1417
+ const fromYaml = globalYaml?.['npmrcAuthFile'];
1418
+ selected =
1419
+ (typeof fromYaml === 'string' ? fromYaml : undefined) ||
1420
+ // The last link is npm's own setting, which npm then reads for itself.
1421
+ (0, utils_1.readEnvVar)(process.env, 'npm_config_userconfig') ||
1422
+ undefined;
1423
+ }
1424
+ return selected
1425
+ ? resolveConfigPath(selected, PNPM_HOME_PATH, root)
1426
+ : (0, path_1.join)((0, os_1.homedir)(), '.npmrc');
1427
+ }
1428
+ /**
1429
+ * The file npm resolves as its own user config. npm documents `userconfig` as
1430
+ * settable from the environment and the command line only, never from another
1431
+ * config file, so its env tier over the `~/.npmrc` default is the whole chain.
1432
+ */
1433
+ function getNpmUserConfigPath(root) {
1434
+ const configured = (0, utils_1.readNpmConfigEnv)(process.env, 'userconfig');
1435
+ return configured
1436
+ ? resolveConfigPath((0, utils_1.expandNpmEnvVars)(configured.trim()), NPM_HOME_PATH, root)
1437
+ : (0, path_1.join)((0, os_1.homedir)(), '.npmrc');
1438
+ }
1439
+ /**
1440
+ * Reports the two credentials the overlay cannot reproduce for the registry npm
1441
+ * is about to contact: one pnpm produces by running a token helper, which npm
1442
+ * has no setting for, and one npm holds in a file of its own that pnpm would
1443
+ * not send. Both supported lines take a helper only from the user config pnpm
1444
+ * resolves (10.x getAuthHeadersFromConfig reads it from userSettings alone; 11
1445
+ * additionally aborts the command outright with TOKEN_HELPER_IN_PROJECT_CONFIG
1446
+ * when one reaches it from any other file), so `userConfigPath` is the one
1447
+ * place worth reading for it.
1448
+ */
1449
+ function reportCredentialDivergences(env, root, scope, userConfigPath, npmrcFiles, pnpmVersion, managerIgnoresEnv) {
1450
+ const projectNpmrc = readNpmrcOrWarn((0, path_1.join)(root, '.npmrc')) ?? new Map();
1451
+ const requestDart = (0, utils_1.requestNerfDart)(contactedRegistry(env, projectNpmrc, scope, managerIgnoresEnv));
1452
+ const npmVisible = npmVisibleReader(env, root, projectNpmrc, managerIgnoresEnv);
1453
+ if (requestDart) {
1454
+ // pnpm goes without one where it resolved the registry but not a credential
1455
+ // for it: a file it discards whole over an unresolvable reference, an entry
1456
+ // it withholds from 11.5.3, or the .npmrc beside an outer workspace file
1457
+ // that it reads in place of the one npm opens here.
1458
+ const pnpmSends = pnpmResolvedReader(env, [...npmrcFiles, { path: userConfigPath, filtered: false }], pnpmVersion, managerIgnoresEnv);
1459
+ (0, utils_1.warnNativeCredential)(env, requestDart, 'pnpm', 'Declare it where pnpm reads it too if it should authenticate there, or remove it from .npmrc if npm should not.', (key) => (pnpmSends(key) ? undefined : npmVisible(key)));
1460
+ }
1461
+ const userConfig = readPnpmNpmrcMap(userConfigPath, pnpmVersion);
1462
+ if (!userConfig || !requestDart) {
1463
+ return;
1464
+ }
1465
+ // Where pnpm pins a `tokenHelper` written without a registry prefix.
1466
+ const pinnedDart = (0, semver_1.gte)(pnpmVersion, '11.4.0')
1467
+ ? // From 11.4.0 it rescopes per file, onto the registry that same file
1468
+ // declares (rescopeUnscopedCreds), expanding `${VAR}` before reading it
1469
+ // off.
1470
+ pnpmNerfDart((0, utils_1.expandPnpmEnvVars)(userConfig.get('registry') ?? '') || DEFAULT_REGISTRY, pnpmVersion)
1471
+ : // 10.x and 11.0-11.3 pin it onto the registry that wins overall instead
1472
+ // (getAuthHeadersFromCreds keys the unscoped credential on the resolved
1473
+ // `registry`). The default registry, never a scoped one: pnpm keys the
1474
+ // helper on `registry` alone, so a scoped package goes elsewhere without
1475
+ // it.
1476
+ (0, utils_1.nerfDart)(npmResolved(env, projectNpmrc, 'registry', managerIgnoresEnv) ||
1477
+ DEFAULT_REGISTRY);
1478
+ const requestKeys = (0, utils_1.registryKeysFor)(requestDart);
1479
+ if (!declaresTokenHelper(userConfig, requestKeys, pinnedDart)) {
1480
+ return;
1481
+ }
1482
+ // A plain credential npm holds beside the helper is one npm still sends, so
1483
+ // there is nothing to report about the helper it never runs.
1484
+ if (!(0, utils_1.hasCredentialFor)(requestDart, npmVisible)) {
1485
+ warnTokenHelper(requestDart);
1486
+ }
1487
+ }
1488
+ /**
1489
+ * Whether the credential pnpm presents for a request comes from a token helper.
1490
+ * A helper outranks every other credential for that registry, whichever layer it
1491
+ * came from (credsToHeader), so finding one settles what pnpm sends.
1492
+ * `requestKeys` is the dart chain pnpm walks, so an unscoped helper counts when
1493
+ * the dart it was pinned to is one of them.
1494
+ */
1495
+ function declaresTokenHelper(userConfig, requestKeys, pinnedDart) {
1496
+ // pnpm expands `${VAR}` in this file's values as well as its keys, and a value
1497
+ // resolving to nothing declares no helper at all.
1498
+ const declared = (key) => (0, utils_1.expandPnpmEnvVars)((0, utils_1.readExpandedKey)(userConfig, key, utils_1.expandPnpmEnvVars) ?? '');
1499
+ return requestKeys.some((key) => !!declared(`${key}:tokenHelper`) ||
1500
+ (key === pinnedDart && !!declared('tokenHelper')));
1501
+ }
1502
+ let warnedTokenHelper = false;
1503
+ // The nerf dart only, for the same reason warnUnscopedCredential uses it. The
1504
+ // helper's command line stays out too: it is what produces the credential.
1505
+ function warnTokenHelper(dart) {
1506
+ if (warnedTokenHelper) {
1507
+ return;
1508
+ }
1509
+ warnedTokenHelper = true;
1510
+ logger_1.logger.warn(`pnpm runs a token helper to authenticate with ${dart}, which npm cannot do, so packages will be fetched from there without that credential. Store the token the helper returns as "${dart}:_authToken=..." in a file npm reads if it should authenticate there.`);
1511
+ }
1512
+ let warnedUnscopedCredential = false;
1513
+ // The nerf dart, not the registry URL: a registry URL can carry its own basic
1514
+ // auth, which would then be in every console and CI log the warning reaches.
1515
+ function warnUnscopedCredential(dart, keys) {
1516
+ if (warnedUnscopedCredential) {
1517
+ return;
1518
+ }
1519
+ warnedUnscopedCredential = true;
1520
+ const scoped = keys.map((key) => `"${dart}:${key}=..."`).join(', ');
1521
+ logger_1.logger.warn(`A credential in pnpm's auth.ini is not scoped to a registry, so it was not used for ${dart} when fetching packages. pnpm pins an unscoped credential to the registry that same file declares, and has deprecated the unscoped form. Scope it (${scoped}) to use it with this registry.`);
1522
+ }
1523
+ /**
1524
+ * Network settings pnpm honors from a yaml configuration file. `caFile`/
1525
+ * `cafile` is the one it accepts and then never uses (it loads a CA file from
1526
+ * the npmrc-family files alone, measured on 10.18.0 and 11.20.0), so that key
1527
+ * is deliberately not bridged; inline `ca`/`cert`/`key` declared here do reach
1528
+ * its fetch, unpinned, exactly as npm's own spelling of them does.
1529
+ */
1530
+ function applyYamlNetworkSettings(env, settings, applyNoProxy = true) {
1531
+ // Only the boolean turns verification off (rejectUnauthorized is
1532
+ // `strictSsl ?? true`, and the agent that carries it is built for
1533
+ // `strictSsl === false`), so any other declared value restores npm's default
1534
+ // over a `strict-ssl=false` from a file below.
1535
+ if (settings.strictSsl !== undefined) {
1536
+ (0, utils_1.setStrictSsl)(env, settings.strictSsl !== false);
1537
+ }
1538
+ if (settings.ca) {
1539
+ env['npm_config_ca'] = settings.ca;
1540
+ }
1541
+ for (const key of ['cert', 'key']) {
1542
+ if (settings[key]) {
1543
+ env[`npm_config_${key}`] = settings[key];
1544
+ }
1545
+ }
1546
+ // pnpm honors either spelling and prefers noProxy when both are set. The
1547
+ // proxies themselves are resolved with every other tier's rather than here.
1548
+ if (applyNoProxy) {
1549
+ (0, utils_1.setProxies)(env, { noProxy: settings.noProxy ?? settings.noproxy });
1550
+ }
1551
+ }