@westbayberry/dg 2.3.3 → 2.3.4
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/README.md +94 -217
- package/dist/agents/copilot-cli.js +7 -2
- package/dist/agents/persistence.js +83 -8
- package/dist/agents/registry.js +5 -2
- package/dist/agents/routing.js +31 -14
- package/dist/api/analyze.js +138 -41
- package/dist/audit/deep.js +2 -1
- package/dist/audit/detectors.js +23 -2
- package/dist/audit/rules.js +4 -1
- package/dist/audit-ui/export.js +5 -4
- package/dist/auth/device-login.js +33 -11
- package/dist/auth/login-app.js +17 -12
- package/dist/auth/store.js +65 -9
- package/dist/bin/dg.js +1 -1
- package/dist/commands/audit.js +12 -20
- package/dist/commands/{guard-commit.js → commit-guard.js} +12 -12
- package/dist/commands/cooldown.js +2 -1
- package/dist/commands/decisions.js +8 -7
- package/dist/commands/help.js +11 -3
- package/dist/commands/licenses.js +17 -8
- package/dist/commands/login.js +58 -21
- package/dist/commands/router.js +12 -2
- package/dist/commands/sbom.js +20 -5
- package/dist/commands/scan.js +5 -4
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +12 -12
- package/dist/commands/status.js +4 -4
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/update.js +20 -0
- package/dist/commands/verify.js +4 -4
- package/dist/config/settings.js +81 -12
- package/dist/export-ui/ExportDialog.js +1 -1
- package/dist/gate/cooldown-request.js +22 -0
- package/dist/gate/egress-guard.js +185 -0
- package/dist/gate/host-match.js +48 -0
- package/dist/gate/verdict-core.js +101 -0
- package/dist/gate/verdict-fetch.js +214 -0
- package/dist/launcher/agent-check.js +1096 -42
- package/dist/launcher/agent-hook-exec.js +8 -1
- package/dist/launcher/agent-hook-io.js +5 -3
- package/dist/launcher/classify.js +110 -26
- package/dist/launcher/env.js +84 -18
- package/dist/launcher/install-preflight.js +26 -3
- package/dist/launcher/live-install.js +4 -2
- package/dist/launcher/manifest-screen.js +88 -7
- package/dist/launcher/output-redaction.js +153 -15
- package/dist/launcher/preflight-prompt.js +1 -1
- package/dist/launcher/resolve-real-binary.js +8 -3
- package/dist/launcher/run.js +237 -40
- package/dist/origin/artifact-cache.js +55 -0
- package/dist/origin/cargo-config.js +12 -0
- package/dist/origin/gate-mode.js +7 -0
- package/dist/origin/platform-paths.js +14 -0
- package/dist/origin/precedence.js +345 -0
- package/dist/origin/server.js +611 -0
- package/dist/origin/spike-worker.js +35 -0
- package/dist/origin/worker.js +116 -0
- package/dist/presentation/package-page.js +21 -1
- package/dist/presentation/safe-version.js +11 -0
- package/dist/presentation/sarif.js +109 -0
- package/dist/project/dgfile.js +54 -17
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/enforcement.js +27 -7
- package/dist/proxy/metadata-map.js +33 -7
- package/dist/proxy/preverified.js +0 -3
- package/dist/proxy/server.js +104 -446
- package/dist/proxy/upstream-proxy.js +23 -4
- package/dist/proxy/worker.js +3 -2
- package/dist/publish-set/collect.js +6 -0
- package/dist/runtime/fatal.js +2 -1
- package/dist/runtime/first-run.js +5 -1
- package/dist/runtime/node-version.js +51 -0
- package/dist/runtime/nudges.js +27 -1
- package/dist/sbom/cyclonedx.js +120 -33
- package/dist/sbom/graph.js +236 -0
- package/dist/sbom/spdx.js +53 -0
- package/dist/sbom-ui/SbomApp.js +5 -3
- package/dist/sbom-ui/inventory.js +8 -10
- package/dist/scan/analyze-worker.js +3 -1
- package/dist/scan/collect.js +38 -8
- package/dist/scan/command.js +77 -27
- package/dist/scan/discovery.js +18 -7
- package/dist/scan/installed.js +202 -0
- package/dist/scan/manifest-coverage.js +128 -0
- package/dist/scan/render.js +125 -40
- package/dist/scan/scanner-report.js +92 -22
- package/dist/scan/staged.js +68 -15
- package/dist/scan/types.js +7 -1
- package/dist/scan-ui/LegacyApp.js +3 -3
- package/dist/scan-ui/components/InteractiveResultsView.js +17 -14
- package/dist/scan-ui/hooks/useScan.js +37 -11
- package/dist/scan-ui/launch.js +2 -2
- package/dist/scan-ui/logo.js +6 -2
- package/dist/scan-ui/shims.js +6 -6
- package/dist/scripts/gate.js +53 -35
- package/dist/security/csv.js +9 -0
- package/dist/security/sanitize.js +11 -2
- package/dist/service/state.js +120 -37
- package/dist/service/trust-refresh.js +12 -1
- package/dist/service/trust-store.js +36 -5
- package/dist/service/worker.js +5 -7
- package/dist/setup/git-hook.js +2 -2
- package/dist/setup/install-method.js +36 -0
- package/dist/setup/plan.js +119 -57
- package/dist/setup/uninstall-standalone.js +7 -2
- package/dist/setup-ui/gate.js +1 -8
- package/dist/setup-ui/offer.js +14 -7
- package/dist/setup-ui/selector.js +1 -1
- package/dist/setup-ui/wizard.js +73 -78
- package/dist/standalone/uninstall.mjs +1114 -386
- package/dist/state/cleanup-registry.js +14 -3
- package/dist/state/cooldown-held.js +36 -13
- package/dist/state/locks.js +239 -34
- package/dist/state/sessions.js +2 -2
- package/dist/util/sh-escape.js +6 -0
- package/dist/util/tty-prompt.js +13 -4
- package/dist/verify/local.js +60 -10
- package/dist/verify/package-check.js +35 -49
- package/dist/verify/preflight.js +59 -33
- package/dist/verify/render.js +11 -12
- package/npm-shrinkwrap.json +714 -213
- package/package.json +3 -2
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
// Materialise a throwaway CARGO_HOME config that replaces crates-io with the
|
|
4
|
+
// local origin's sparse endpoint. cargo has no env var to override the default
|
|
5
|
+
// registry, so this is the only way to route it env-only — the user's real
|
|
6
|
+
// ~/.cargo/config.toml is never touched.
|
|
7
|
+
export function writeCargoOriginConfig(cargoHome, originUrl) {
|
|
8
|
+
mkdirSync(cargoHome, { recursive: true });
|
|
9
|
+
const sparse = `sparse+${originUrl.endsWith("/") ? originUrl : `${originUrl}/`}`;
|
|
10
|
+
const config = `[source.crates-io]\nreplace-with = "dg-origin"\n\n[source.dg-origin]\nregistry = "${sparse}"\n`;
|
|
11
|
+
writeFileSync(join(cargoHome, "config.toml"), config, { encoding: "utf8", mode: 0o600 });
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The kill-switch: revert to the proxy door without a re-publish. Kept for at
|
|
2
|
+
// least one release so an installed base can fall back if the origin path
|
|
3
|
+
// regresses. It disables the ORIGIN, not the gate — the proxy still screens.
|
|
4
|
+
export function isRegistryGateDisabled(env) {
|
|
5
|
+
const raw = env.DG_DISABLE_REGISTRY_GATE;
|
|
6
|
+
return raw === "1" || raw === "true";
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
export function originPlatform(platform = process.platform) {
|
|
3
|
+
return platform === "win32" ? "win32" : "posix";
|
|
4
|
+
}
|
|
5
|
+
// pip reads %APPDATA%\pip\pip.ini on Windows; dg never writes it (env-only via
|
|
6
|
+
// PIP_INDEX_URL/PIP_TRUSTED_HOST), but doctor needs the path to detect a
|
|
7
|
+
// conflicting user config that would out-rank the env.
|
|
8
|
+
export function pipUserConfigPath(env, platform = originPlatform()) {
|
|
9
|
+
if (platform === "win32") {
|
|
10
|
+
const base = env.APPDATA ?? join(env.USERPROFILE ?? "", "AppData", "Roaming");
|
|
11
|
+
return join(base, "pip", "pip.ini");
|
|
12
|
+
}
|
|
13
|
+
return join(env.HOME ?? "", ".config", "pip", "pip.conf");
|
|
14
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { pipUserConfigPath } from "./platform-paths.js";
|
|
4
|
+
import { readPipRequirementSpecs } from "../launcher/manifest-screen.js";
|
|
5
|
+
// CLI flags that point a manager at a different registry/index than the env var
|
|
6
|
+
// the origin sets. A CLI value out-ranks env for every manager, so any of these
|
|
7
|
+
// means the origin is not actually in front of (some of) the fetches.
|
|
8
|
+
const OVERRIDE_FLAGS = {
|
|
9
|
+
javascript: new Set(["--registry"]),
|
|
10
|
+
python: new Set(["--index-url", "-i", "--extra-index-url", "--index", "--default-index", "--find-links", "-f"]),
|
|
11
|
+
// cargo --config can inline a `source.crates-io.replace-with` (or point at a
|
|
12
|
+
// config file) that redirects the crates-io source past the origin door.
|
|
13
|
+
rust: new Set(["--registry", "--index", "--config"]),
|
|
14
|
+
gated: new Set()
|
|
15
|
+
};
|
|
16
|
+
// pip/uv (optparse) cluster short flags and may glue a value flag's value onto
|
|
17
|
+
// the token: `-ihttps://evil`, `-vi https://evil` (=`-v -i …`), `-vihttps://evil`.
|
|
18
|
+
// A value flag can sit anywhere in the cluster, so scan each two-char short flag
|
|
19
|
+
// and return the first override before the rest is read as its value. Over-
|
|
20
|
+
// detecting only routes the install to the proxy door (which gates all
|
|
21
|
+
// transport), so a false match is safe. optparse also accepts an unambiguous
|
|
22
|
+
// prefix of a long option (`--index-u` => `--index-url`); treat any `--` token
|
|
23
|
+
// that prefixes an override flag as that flag so the abbreviation can't slip by.
|
|
24
|
+
function overrideFlagToken(arg, flags) {
|
|
25
|
+
if (/^-[a-zA-Z]/.test(arg) && !arg.startsWith("--") && arg.length > 2) {
|
|
26
|
+
for (let i = 1; i < arg.length; i++) {
|
|
27
|
+
const short = `-${arg[i]}`;
|
|
28
|
+
if (flags.has(short)) {
|
|
29
|
+
return short;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const head = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
34
|
+
if (head.startsWith("--") && head.length > 2 && !flags.has(head)) {
|
|
35
|
+
for (const flag of flags) {
|
|
36
|
+
if (flag.startsWith("--") && flag.startsWith(head)) {
|
|
37
|
+
return flag;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return head;
|
|
42
|
+
}
|
|
43
|
+
export function resolveOriginPrecedence(classification, env, cwd) {
|
|
44
|
+
const overrides = [];
|
|
45
|
+
let active = true;
|
|
46
|
+
const flags = OVERRIDE_FLAGS[classification.ecosystem];
|
|
47
|
+
for (const arg of classification.args) {
|
|
48
|
+
const flag = overrideFlagToken(arg, flags);
|
|
49
|
+
if (flags.has(flag)) {
|
|
50
|
+
overrides.push(`command-line ${flag}`);
|
|
51
|
+
active = false;
|
|
52
|
+
}
|
|
53
|
+
else if (classification.ecosystem === "javascript" && /^--@[^:=\s]+:registry$/.test(flag)) {
|
|
54
|
+
// `--@scope:registry=URL` points that scope at its own upstream the origin
|
|
55
|
+
// does not front; fall back to the proxy door, which gates all transport.
|
|
56
|
+
overrides.push(`command-line ${flag} routes a scope to its own registry`);
|
|
57
|
+
active = false;
|
|
58
|
+
}
|
|
59
|
+
else if (classification.ecosystem === "javascript" && /^--config\.(@[^:=\s]+:)?registry$/.test(flag)) {
|
|
60
|
+
// pnpm `--config.registry=URL` / `--config.@scope:registry=URL` sets the
|
|
61
|
+
// registry the same as the npm config key.
|
|
62
|
+
overrides.push(`command-line ${flag} overrides the registry`);
|
|
63
|
+
active = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (classification.ecosystem === "javascript") {
|
|
67
|
+
for (const scope of scopedNpmRegistries(env, cwd)) {
|
|
68
|
+
overrides.push(`${scope} routes to its own registry (not gated by dg)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// The origin door points pip/uv at the loopback index via env, but pip also
|
|
72
|
+
// MERGES extra-index-url / find-links and a non-default index-url from its
|
|
73
|
+
// config file (pip.conf), which the env does not override. Detect those so the
|
|
74
|
+
// install falls back to the proxy door instead of fetching from an un-fronted
|
|
75
|
+
// source while dg reports the gate active.
|
|
76
|
+
if (classification.ecosystem === "python") {
|
|
77
|
+
for (const reason of pipConfigIndexes(env, cwd)) {
|
|
78
|
+
overrides.push(reason);
|
|
79
|
+
active = false;
|
|
80
|
+
}
|
|
81
|
+
// pip also honors --index-url / --extra-index-url / -f declared INSIDE a
|
|
82
|
+
// requirements/constraints file (and any nested -r/-c include). Reuse the
|
|
83
|
+
// static hook's include-walk so the runtime gate sees the same in-file index
|
|
84
|
+
// directives and falls back to the proxy door instead of fetching un-fronted.
|
|
85
|
+
const requirementIndexes = readPipRequirementSpecs(classification.args, cwd)?.alternateIndexes ?? [];
|
|
86
|
+
for (const idx of requirementIndexes) {
|
|
87
|
+
if (isNonDefaultPythonIndex(idx)) {
|
|
88
|
+
overrides.push(`requirements-file index ${idx} (origin fronts the public index only)`);
|
|
89
|
+
active = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// cargo merges config from the project `.cargo/config.toml` (walking up from
|
|
94
|
+
// cwd) and the user CARGO_HOME, with the project file out-ranking the throwaway
|
|
95
|
+
// CARGO_HOME the origin door writes. A `[source.crates-io] replace-with` there
|
|
96
|
+
// shadows the dg-origin source, so the crates install un-gated — detect it and
|
|
97
|
+
// fall back to the proxy door.
|
|
98
|
+
if (classification.ecosystem === "rust") {
|
|
99
|
+
for (const reason of cargoConfigOverride(env, cwd)) {
|
|
100
|
+
overrides.push(reason);
|
|
101
|
+
active = false;
|
|
102
|
+
}
|
|
103
|
+
for (const reason of cargoEnvOverride(env)) {
|
|
104
|
+
overrides.push(reason);
|
|
105
|
+
active = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// The origin chains only to the PUBLIC default registry/index. A configured
|
|
109
|
+
// custom primary upstream (corporate Nexus/Verdaccio, a private index) is one
|
|
110
|
+
// the origin cannot front correctly, so it counts as an override and the proxy
|
|
111
|
+
// door — which intercepts any upstream at the transport layer — takes over.
|
|
112
|
+
const customUpstream = nonDefaultPrimaryUpstream(classification, env, cwd);
|
|
113
|
+
if (customUpstream) {
|
|
114
|
+
overrides.push(customUpstream);
|
|
115
|
+
active = false;
|
|
116
|
+
}
|
|
117
|
+
// An EXTRA index sits alongside the primary one the origin fronts; pip/uv query
|
|
118
|
+
// it directly, so any package it serves is fetched un-gated. The origin cannot
|
|
119
|
+
// front it, so the proxy door (which intercepts all transport) takes over.
|
|
120
|
+
for (const extra of extraPythonIndexes(classification, env)) {
|
|
121
|
+
overrides.push(extra);
|
|
122
|
+
active = false;
|
|
123
|
+
}
|
|
124
|
+
return { active, overrides };
|
|
125
|
+
}
|
|
126
|
+
function extraPythonIndexes(classification, env) {
|
|
127
|
+
if (classification.ecosystem !== "python") {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const found = [];
|
|
131
|
+
for (const key of ["PIP_EXTRA_INDEX_URL", "PIP_FIND_LINKS", "UV_EXTRA_INDEX_URL", "UV_INDEX", "UV_FIND_LINKS"]) {
|
|
132
|
+
const value = env[key]?.trim();
|
|
133
|
+
if (value) {
|
|
134
|
+
found.push(`${key} adds a package source the origin does not front`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return found;
|
|
138
|
+
}
|
|
139
|
+
function nonDefaultPrimaryUpstream(classification, env, cwd) {
|
|
140
|
+
if (classification.ecosystem === "javascript") {
|
|
141
|
+
const registry = env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY ?? npmrcPrimaryRegistry(env, cwd);
|
|
142
|
+
if (registry && !isHost(registry, "registry.npmjs.org")) {
|
|
143
|
+
return `non-default npm registry ${registry} (origin fronts the public registry only)`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (classification.ecosystem === "python") {
|
|
147
|
+
const index = env.PIP_INDEX_URL ?? env.UV_DEFAULT_INDEX ?? env.UV_INDEX_URL;
|
|
148
|
+
if (index && !isHost(index, "pypi.org")) {
|
|
149
|
+
return `non-default python index ${index} (origin fronts the public index only)`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function npmrcPrimaryRegistry(env, cwd) {
|
|
155
|
+
for (const file of [join(cwd, ".npmrc"), join(env.HOME ?? "", ".npmrc")]) {
|
|
156
|
+
for (const line of readLines(file)) {
|
|
157
|
+
const match = /^\s*registry\s*=\s*(\S+)/.exec(line);
|
|
158
|
+
if (match?.[1]) {
|
|
159
|
+
return match[1];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function isNonDefaultPythonIndex(value) {
|
|
166
|
+
try {
|
|
167
|
+
const host = new URL(value).hostname;
|
|
168
|
+
return host !== "pypi.org" && host !== "files.pythonhosted.org";
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// A bare/relative local path (`--find-links ./wheels`) is not an un-fronted
|
|
172
|
+
// remote index, so it does not force a fallback.
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isHost(url, host) {
|
|
177
|
+
try {
|
|
178
|
+
return new URL(url).hostname === host;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function scopedNpmRegistries(env, cwd) {
|
|
185
|
+
const scopes = new Set();
|
|
186
|
+
for (const file of [join(cwd, ".npmrc"), join(env.HOME ?? "", ".npmrc")]) {
|
|
187
|
+
for (const line of readLines(file)) {
|
|
188
|
+
const match = /^\s*(@[^:=\s]+):registry\s*=/.exec(line);
|
|
189
|
+
if (match?.[1]) {
|
|
190
|
+
scopes.add(match[1]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// npm/pnpm also accept a scoped registry from the environment
|
|
195
|
+
// (npm_config_@scope:registry); that scope resolves to its own upstream, which
|
|
196
|
+
// the origin does not front.
|
|
197
|
+
for (const key of Object.keys(env)) {
|
|
198
|
+
const match = /^npm_config_(@[^:=\s]+):registry$/i.exec(key);
|
|
199
|
+
if (match?.[1] && (env[key] ?? "").trim()) {
|
|
200
|
+
scopes.add(match[1]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return [...scopes];
|
|
204
|
+
}
|
|
205
|
+
// pip.conf (INI): index-url to a non-pypi host, or any extra-index-url /
|
|
206
|
+
// find-links, is a package source the origin does not front.
|
|
207
|
+
function pipConfigIndexes(env, cwd) {
|
|
208
|
+
const out = [];
|
|
209
|
+
const home = env.HOME ?? "";
|
|
210
|
+
const venv = env.VIRTUAL_ENV;
|
|
211
|
+
// pip's full config search chain — global, user (XDG + legacy), and the active
|
|
212
|
+
// virtualenv's site config. An extra-index-url in ANY of these is merged into
|
|
213
|
+
// the install, so all must be checked or a config-file index slips the gate.
|
|
214
|
+
const files = [
|
|
215
|
+
env.PIP_CONFIG_FILE,
|
|
216
|
+
"/etc/pip.conf",
|
|
217
|
+
"/etc/xdg/pip/pip.conf",
|
|
218
|
+
pipUserConfigPath(env),
|
|
219
|
+
home ? join(home, ".pip", "pip.conf") : undefined,
|
|
220
|
+
venv ? join(venv, "pip.conf") : undefined,
|
|
221
|
+
join(cwd, "pip.conf"),
|
|
222
|
+
].filter((f) => Boolean(f));
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
let section = "";
|
|
225
|
+
for (const raw of readLines(file)) {
|
|
226
|
+
const line = raw.trim();
|
|
227
|
+
if (line.startsWith("#") || line.startsWith(";")) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const sec = /^\[([^\]]+)\]/.exec(line);
|
|
231
|
+
if (sec) {
|
|
232
|
+
section = sec[1] ?? "";
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (section !== "global" && section !== "install") {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const kv = /^(index-url|extra-index-url|find-links)\s*[=:]\s*(\S.*)$/.exec(line);
|
|
239
|
+
if (!kv) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const value = (kv[2] ?? "").trim();
|
|
243
|
+
if (kv[1] === "index-url") {
|
|
244
|
+
if (!isHost(value, "pypi.org")) {
|
|
245
|
+
out.push(`pip.conf index-url ${value} (origin fronts the public index only)`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
out.push(`pip.conf ${kv[1]} adds a package source the origin does not front`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
function normalizeTomlKey(s) {
|
|
256
|
+
return s
|
|
257
|
+
.split(".")
|
|
258
|
+
.map((seg) => seg.trim().replace(/^["']|["']$/g, "").trim())
|
|
259
|
+
.join(".");
|
|
260
|
+
}
|
|
261
|
+
// cargo source/registry redirection in a config file out-ranks the throwaway
|
|
262
|
+
// CARGO_HOME the origin door writes, so a `replace-with` (or a direct crates-io
|
|
263
|
+
// registry override) means the origin is shadowed. cargo accepts the redirection
|
|
264
|
+
// in several equivalent TOML spellings (header table, dotted key under a parent
|
|
265
|
+
// table, fully-qualified top-level key, inline table, whitespace-padded header);
|
|
266
|
+
// normalize each assignment to its fully-qualified key so every spelling is seen.
|
|
267
|
+
function cargoConfigOverride(env, cwd) {
|
|
268
|
+
const files = [];
|
|
269
|
+
let dir = cwd;
|
|
270
|
+
for (let depth = 0; depth < 40; depth += 1) {
|
|
271
|
+
files.push(join(dir, ".cargo", "config.toml"), join(dir, ".cargo", "config"));
|
|
272
|
+
const parent = dirname(dir);
|
|
273
|
+
if (parent === dir) {
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
dir = parent;
|
|
277
|
+
}
|
|
278
|
+
const cargoHome = env.CARGO_HOME && env.CARGO_HOME.length > 0 ? env.CARGO_HOME : join(env.HOME ?? "", ".cargo");
|
|
279
|
+
files.push(join(cargoHome, "config.toml"), join(cargoHome, "config"));
|
|
280
|
+
for (const file of files) {
|
|
281
|
+
let section = "";
|
|
282
|
+
for (const raw of readLines(file)) {
|
|
283
|
+
const line = raw.trim();
|
|
284
|
+
if (line.startsWith("#") || line.length === 0) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const sec = /^\[([^\]]+)\]/.exec(line);
|
|
288
|
+
if (sec) {
|
|
289
|
+
section = normalizeTomlKey(sec[1] ?? "");
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const eq = line.indexOf("=");
|
|
293
|
+
if (eq < 0) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const lhs = line.slice(0, eq);
|
|
297
|
+
const rhs = line.slice(eq + 1);
|
|
298
|
+
const fqKey = normalizeTomlKey(section ? `${section}.${lhs}` : lhs);
|
|
299
|
+
if (fqKey === "source.crates-io.replace-with") {
|
|
300
|
+
return [`cargo config ${file} replaces the crates-io source (origin door shadowed)`];
|
|
301
|
+
}
|
|
302
|
+
if (fqKey === "source.crates-io.registry") {
|
|
303
|
+
return [`cargo config ${file} overrides the crates-io registry`];
|
|
304
|
+
}
|
|
305
|
+
if (fqKey === "registries.crates-io.index") {
|
|
306
|
+
return [`cargo config ${file} overrides the crates-io index`];
|
|
307
|
+
}
|
|
308
|
+
// Inline-table assignment to the table itself: `crates-io = { replace-with = … }`.
|
|
309
|
+
if (fqKey === "source.crates-io" && /\breplace-with\s*=/.test(rhs)) {
|
|
310
|
+
return [`cargo config ${file} replaces the crates-io source (origin door shadowed)`];
|
|
311
|
+
}
|
|
312
|
+
if (fqKey === "source.crates-io" && /\bregistry\s*=/.test(rhs)) {
|
|
313
|
+
return [`cargo config ${file} overrides the crates-io registry`];
|
|
314
|
+
}
|
|
315
|
+
if (fqKey === "registries.crates-io" && /\bindex\s*=/.test(rhs)) {
|
|
316
|
+
return [`cargo config ${file} overrides the crates-io index`];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
// cargo also accepts registry redirection through env vars (CARGO_REGISTRY_DEFAULT,
|
|
323
|
+
// CARGO_SOURCE_CRATES_IO_REPLACE_WITH, CARGO_REGISTRIES_CRATES_IO_INDEX), which the
|
|
324
|
+
// throwaway CARGO_HOME config cannot win against.
|
|
325
|
+
function cargoEnvOverride(env) {
|
|
326
|
+
const out = [];
|
|
327
|
+
if (env.CARGO_REGISTRY_DEFAULT && env.CARGO_REGISTRY_DEFAULT.trim()) {
|
|
328
|
+
out.push("CARGO_REGISTRY_DEFAULT overrides the default registry");
|
|
329
|
+
}
|
|
330
|
+
if (env.CARGO_SOURCE_CRATES_IO_REPLACE_WITH && env.CARGO_SOURCE_CRATES_IO_REPLACE_WITH.trim()) {
|
|
331
|
+
out.push("CARGO_SOURCE_CRATES_IO_REPLACE_WITH replaces the crates-io source");
|
|
332
|
+
}
|
|
333
|
+
if (env.CARGO_REGISTRIES_CRATES_IO_INDEX && env.CARGO_REGISTRIES_CRATES_IO_INDEX.trim()) {
|
|
334
|
+
out.push("CARGO_REGISTRIES_CRATES_IO_INDEX overrides the crates-io index");
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
function readLines(file) {
|
|
339
|
+
try {
|
|
340
|
+
return readFileSync(file, "utf8").split("\n");
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
}
|