nx 23.1.1 → 23.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/release/changelog-renderer/index.js +4 -5
- package/dist/release/index.d.ts +1 -1
- package/dist/src/command-line/add/add.js +5 -0
- package/dist/src/command-line/affected/affected.js +1 -0
- package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
- package/dist/src/command-line/import/import.js +1 -2
- package/dist/src/command-line/init/init-v2.js +4 -5
- package/dist/src/command-line/migrate/migrate.d.ts +4 -0
- package/dist/src/command-line/migrate/migrate.js +89 -26
- package/dist/src/command-line/release/changelog.js +5 -0
- package/dist/src/command-line/release/index.d.ts +1 -1
- package/dist/src/command-line/release/utils/git.js +1 -1
- package/dist/src/command-line/release/utils/release-graph.d.ts +17 -0
- package/dist/src/command-line/release/utils/release-graph.js +71 -19
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +6 -0
- package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
- package/dist/src/command-line/release/version/release-group-processor.js +80 -6
- package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
- package/dist/src/command-line/release/version/version-actions.js +9 -1
- package/dist/src/command-line/release/version.js +9 -5
- package/dist/src/command-line/reset/reset.js +6 -1
- package/dist/src/command-line/run/run-one.js +1 -0
- package/dist/src/command-line/run-many/run-many.js +1 -0
- package/dist/src/command-line/watch/watch.js +13 -0
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/core/graph/styles.js +1 -1
- package/dist/src/daemon/client/client.d.ts +52 -1
- package/dist/src/daemon/client/client.js +160 -36
- package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
- package/dist/src/daemon/client/daemon-environment.js +110 -2
- package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
- package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
- package/dist/src/daemon/message-types/daemon-message.js +25 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
- package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
- package/dist/src/daemon/server/latest-nx.js +5 -2
- package/dist/src/daemon/server/server.js +24 -0
- package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
- package/dist/src/daemon/server/shutdown-utils.js +11 -4
- package/dist/src/daemon/socket-utils.d.ts +1 -0
- package/dist/src/daemon/socket-utils.js +24 -3
- package/dist/src/daemon/tmp-dir.d.ts +43 -3
- package/dist/src/daemon/tmp-dir.js +437 -15
- package/dist/src/devkit-internals.d.ts +93 -3
- package/dist/src/devkit-internals.js +192 -5
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
- package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
- package/dist/src/generators/tree.d.ts +4 -2
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
- package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
- package/dist/src/native/index.d.ts +10 -0
- package/dist/src/native/index.js +71 -28
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/native-file-cache-location.d.ts +17 -1
- package/dist/src/native/native-file-cache-location.js +86 -12
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
- package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
- package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +60 -0
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +14 -8
- package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +2 -0
- package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
- package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
- package/dist/src/project-graph/error-types.d.ts +12 -1
- package/dist/src/project-graph/error-types.js +27 -2
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
- package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
- package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
- package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
- package/dist/src/project-graph/project-graph.js +20 -0
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +5 -1
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +133 -3
- package/dist/src/tasks-runner/task-env.js +5 -2
- package/dist/src/tasks-runner/utils.js +2 -6
- package/dist/src/utils/assert-workspace-validity.js +7 -2
- package/dist/src/utils/fileutils.d.ts +5 -0
- package/dist/src/utils/fileutils.js +7 -2
- package/dist/src/utils/git-worktrees.d.ts +30 -0
- package/dist/src/utils/git-worktrees.js +212 -0
- package/dist/src/utils/ignore.d.ts +128 -1
- package/dist/src/utils/ignore.js +182 -12
- package/dist/src/utils/long-running-target.d.ts +10 -0
- package/dist/src/utils/long-running-target.js +19 -0
- package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
- package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
- package/dist/src/utils/min-release-age/packument.js +1 -1
- package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
- package/dist/src/utils/nx-tmp-dir.js +40 -0
- package/dist/src/utils/owned-private-dir.d.ts +132 -0
- package/dist/src/utils/owned-private-dir.js +323 -0
- package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
- package/dist/src/utils/package-manager-config/bunfig.js +49 -0
- package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
- package/dist/src/utils/package-manager-config/npmrc.js +124 -0
- package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
- package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
- package/dist/src/utils/package-manager.d.ts +21 -2
- package/dist/src/utils/package-manager.js +244 -47
- package/dist/src/utils/provenance.js +6 -14
- package/dist/src/utils/registry-config/bun.d.ts +2 -0
- package/dist/src/utils/registry-config/bun.js +257 -0
- package/dist/src/utils/registry-config/index.d.ts +13 -0
- package/dist/src/utils/registry-config/index.js +102 -0
- package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
- package/dist/src/utils/registry-config/pnpm.js +1551 -0
- package/dist/src/utils/registry-config/utils.d.ts +195 -0
- package/dist/src/utils/registry-config/utils.js +496 -0
- package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-berry.js +612 -0
- package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
- package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
- package/dist/src/utils/safe-spawn.d.ts +24 -0
- package/dist/src/utils/safe-spawn.js +104 -0
- package/dist/src/utils/shell-quoting.d.ts +15 -0
- package/dist/src/utils/shell-quoting.js +35 -0
- package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
- package/dist/src/utils/wait-for-socket-connection.js +14 -8
- package/migrations.json +6 -0
- package/package.json +12 -12
- package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
- package/dist/src/utils/min-release-age/npmrc.js +0 -45
|
@@ -0,0 +1,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
|
+
}
|