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