@westbayberry/dg 2.1.0 → 2.3.0
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 +216 -226
- package/dist/agents/claude-code.js +123 -0
- package/dist/agents/codex.js +65 -0
- package/dist/agents/copilot-cli.js +115 -0
- package/dist/agents/cursor.js +118 -0
- package/dist/agents/gate-posture.js +21 -0
- package/dist/agents/gemini.js +107 -0
- package/dist/agents/persistence.js +351 -0
- package/dist/agents/registry.js +128 -0
- package/dist/agents/routing.js +118 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/windsurf.js +93 -0
- package/dist/api/analyze.js +1 -1
- package/dist/audit/detectors.js +0 -11
- package/dist/audit/events.js +5 -21
- package/dist/audit/rules.js +6 -2
- package/dist/audit-ui/AuditApp.js +2 -0
- package/dist/audit-ui/components/AuditResultsView.js +55 -92
- package/dist/audit-ui/export.js +0 -4
- package/dist/audit-ui/format.js +0 -3
- package/dist/audit-ui/launch.js +10 -1
- package/dist/auth/device-login.js +4 -5
- package/dist/auth/login-app.js +7 -7
- package/dist/auth/store.js +17 -5
- package/dist/bin/dg.js +59 -52
- package/dist/commands/agents.js +275 -0
- package/dist/commands/audit.js +28 -3
- package/dist/commands/config.js +15 -7
- package/dist/commands/cooldown.js +389 -0
- package/dist/commands/decisions.js +23 -9
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/help.js +1 -1
- package/dist/commands/licenses.js +10 -22
- package/dist/commands/logout.js +4 -11
- package/dist/commands/router.js +6 -4
- package/dist/commands/sbom.js +206 -0
- package/dist/commands/scan.js +1 -1
- package/dist/commands/service.js +32 -13
- package/dist/commands/setup.js +196 -27
- package/dist/commands/types.js +1 -0
- package/dist/commands/uninstall.js +2 -1
- package/dist/commands/update.js +17 -8
- package/dist/commands/verify.js +8 -5
- package/dist/config/settings.js +49 -48
- package/dist/decisions/remember-prompt.js +5 -10
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +5 -4
- package/dist/install-ui/block-render.js +18 -14
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/launcher/agent-check.js +896 -0
- package/dist/launcher/agent-hook-exec.js +70 -0
- package/dist/launcher/agent-hook-io.js +39 -0
- package/dist/launcher/cargo-cache.js +40 -0
- package/dist/launcher/classify.js +44 -7
- package/dist/launcher/env.js +105 -26
- package/dist/launcher/install-preflight.js +64 -18
- package/dist/launcher/live-install.js +29 -8
- package/dist/launcher/manifest-screen.js +171 -0
- package/dist/launcher/output-redaction.js +10 -4
- package/dist/launcher/preflight-prompt.js +18 -4
- package/dist/launcher/run.js +178 -103
- package/dist/launcher/spawn-invocation.js +21 -0
- package/dist/policy/cooldown.js +18 -5
- package/dist/policy/evaluate.js +5 -6
- package/dist/policy/pypi-name.js +17 -0
- package/dist/presentation/mode.js +3 -2
- package/dist/presentation/package-page.js +9 -0
- package/dist/presentation/theme.js +7 -7
- package/dist/project/dgfile.js +146 -6
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/auth.js +42 -0
- package/dist/proxy/ca.js +29 -9
- package/dist/proxy/cooldown-exemptions-file.js +33 -0
- package/dist/proxy/enforcement.js +55 -16
- package/dist/proxy/metadata-map.js +66 -12
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +568 -53
- package/dist/proxy/worker.js +16 -1
- package/dist/publish-set/collect.js +1 -4
- package/dist/publish-set/npm.js +8 -5
- package/dist/publish-set/pack.js +9 -3
- package/dist/runtime/cli.js +0 -4
- package/dist/runtime/fatal.js +31 -0
- package/dist/runtime/first-run.js +14 -12
- package/dist/runtime/node-version.js +43 -6
- package/dist/runtime/nudges.js +35 -2
- package/dist/sbom/cyclonedx.js +211 -0
- package/dist/sbom-ui/SbomApp.js +158 -0
- package/dist/sbom-ui/components/SbomHeader.js +32 -0
- package/dist/sbom-ui/components/SbomList.js +52 -0
- package/dist/sbom-ui/inventory.js +132 -0
- package/dist/sbom-ui/launch.js +51 -0
- package/dist/sbom-ui/run.js +55 -0
- package/dist/sbom-ui/store.js +26 -0
- package/dist/scan/collect.js +10 -6
- package/dist/scan/command.js +17 -10
- package/dist/scan/discovery.js +11 -2
- package/dist/scan/render.js +28 -4
- package/dist/scan/scanner-report.js +15 -9
- package/dist/scan/staged.js +33 -9
- package/dist/scan-ui/LegacyApp.js +9 -13
- package/dist/scan-ui/alt-screen.js +5 -8
- package/dist/scan-ui/components/InteractiveResultsView.js +133 -120
- package/dist/scan-ui/components/ProgressBar.js +3 -14
- package/dist/scan-ui/components/ProjectSelector.js +1 -1
- package/dist/scan-ui/components/ScoreHeader.js +2 -3
- package/dist/scan-ui/components/SetupBanner.js +0 -6
- package/dist/scan-ui/format-helpers.js +61 -5
- package/dist/scan-ui/hooks/useResizeRepaint.js +25 -0
- package/dist/scan-ui/hooks/useScan.js +19 -2
- package/dist/scan-ui/launch.js +7 -4
- package/dist/scan-ui/shims.js +11 -4
- package/dist/scripts/detect.js +7 -2
- package/dist/scripts/gate.js +1 -1
- package/dist/security/sanitize.js +8 -4
- package/dist/service/state.js +28 -9
- package/dist/service/trust-refresh.js +92 -0
- package/dist/service/trust-store.js +5 -9
- package/dist/service/worker.js +23 -1
- package/dist/setup/activate-shell.js +28 -0
- package/dist/setup/git-hook.js +49 -4
- package/dist/setup/plan.js +227 -49
- package/dist/setup/uninstall-standalone.js +25 -0
- package/dist/setup-ui/gate.js +39 -0
- package/dist/setup-ui/offer.js +42 -0
- package/dist/setup-ui/selector.js +27 -0
- package/dist/setup-ui/tasks.js +56 -0
- package/dist/setup-ui/wizard.js +233 -0
- package/dist/standalone/uninstall.mjs +2123 -0
- package/dist/state/cooldown-held.js +66 -0
- package/dist/state/index.js +1 -0
- package/dist/state/locks.js +4 -2
- package/dist/state/store.js +2 -1
- package/dist/util/external-tool.js +25 -0
- package/dist/util/git.js +10 -3
- package/dist/util/report-writer.js +57 -0
- package/dist/verify/local.js +242 -44
- package/dist/verify/package-check.js +87 -29
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +7 -8
- package/NOTICE +0 -5
- package/dist/commands/completion.js +0 -117
- package/dist/commands/explain.js +0 -236
- package/dist/commands/unavailable.js +0 -11
- package/dist/scripts/rebuild.js +0 -28
- package/dist/telemetry/events.js +0 -40
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { runAgentHookExec } from "./agent-hook-io.js";
|
|
3
|
+
import { getAgent, isAgentId } from "../agents/registry.js";
|
|
4
|
+
import { resolveDgPaths } from "../state/index.js";
|
|
5
|
+
import { writeJsonAtomic } from "../util/json-file.js";
|
|
6
|
+
async function readStdin(stream = process.stdin) {
|
|
7
|
+
if (stream.isTTY) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for await (const chunk of stream) {
|
|
12
|
+
chunks.push(chunk);
|
|
13
|
+
}
|
|
14
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
15
|
+
}
|
|
16
|
+
function recordFixture(agent, stdin) {
|
|
17
|
+
const path = join(resolveDgPaths().stateDir, "fixtures", `${agent}-${Date.now()}.json`);
|
|
18
|
+
writeJsonAtomic(path, { agent, capturedAt: new Date().toISOString(), stdin });
|
|
19
|
+
return path;
|
|
20
|
+
}
|
|
21
|
+
// Short-circuited before any banner/nudge so stdout carries only the decision
|
|
22
|
+
// JSON the agent parses.
|
|
23
|
+
export async function maybeAgentHookExec(args, opts = {}) {
|
|
24
|
+
const noop = { exitCode: 0, stdout: "", stderr: "" };
|
|
25
|
+
if (args[0] !== "hook-exec") {
|
|
26
|
+
return { handled: false, result: noop };
|
|
27
|
+
}
|
|
28
|
+
const agent = args[1];
|
|
29
|
+
if (!agent || !isAgentId(agent)) {
|
|
30
|
+
// Unknown agent (e.g. a hook written by a newer dg, run by an older one):
|
|
31
|
+
// fail closed. We can't emit this agent's allow shape, and a non-zero exit
|
|
32
|
+
// with no decision blocks across the agents we support, so an out-of-date
|
|
33
|
+
// dg refuses the install with a clear message rather than waving it through.
|
|
34
|
+
return {
|
|
35
|
+
handled: true,
|
|
36
|
+
result: {
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
stdout: "",
|
|
39
|
+
stderr: `dg hook-exec: unknown agent '${agent ?? ""}' — blocked under the firewall; update dg (npm i -g @westbayberry/dg)\n`
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let stdin;
|
|
44
|
+
try {
|
|
45
|
+
stdin = await readStdin(opts.stdin);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// A stdin stream error must not escape to the fatal handler (exit 70 with
|
|
49
|
+
// empty stdout reads as allow to every agent). Emit this agent's deny shape.
|
|
50
|
+
const emitted = getAgent(agent).emitDecision({
|
|
51
|
+
decision: "deny",
|
|
52
|
+
reason: "dg hook: could not read the tool payload from stdin; blocked under the firewall",
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
handled: true,
|
|
56
|
+
result: { exitCode: emitted.exitCode || 2, stdout: emitted.stdout, stderr: "dg hook: stdin read error\n" },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
let fixtureNote = "";
|
|
60
|
+
if (args[2] === "--record-fixture") {
|
|
61
|
+
try {
|
|
62
|
+
fixtureNote = `dg hook-exec: recorded fixture ${recordFixture(agent, stdin)}\n`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
fixtureNote = "dg hook-exec: could not record the fixture\n";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const result = await runAgentHookExec(agent, stdin);
|
|
69
|
+
return { handled: true, result: { exitCode: result.exitCode, stdout: result.stdout, stderr: `${fixtureNote}${result.stderr}` } };
|
|
70
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { agentCheckCommand, ESCALATE, formatScreenedNote } from "./agent-check.js";
|
|
2
|
+
import { getAgent } from "../agents/registry.js";
|
|
3
|
+
import { redactSecrets } from "./output-redaction.js";
|
|
4
|
+
export async function runAgentHookExec(agent, stdin, deps = {}) {
|
|
5
|
+
const integration = getAgent(agent);
|
|
6
|
+
const parsed = integration.parseInput(stdin);
|
|
7
|
+
if (!parsed) {
|
|
8
|
+
// Unreadable payload -> fail closed (deny) so a malformed hook input can
|
|
9
|
+
// never slip an install through unverified.
|
|
10
|
+
const verdict = { decision: "deny", reason: `dg hook: could not read the tool command, so the install is blocked. ${ESCALATE}` };
|
|
11
|
+
const emitted = integration.emitDecision(verdict);
|
|
12
|
+
return { stdout: emitted.stdout, stderr: "dg hook: malformed hook payload\n", exitCode: emitted.exitCode };
|
|
13
|
+
}
|
|
14
|
+
let verdict;
|
|
15
|
+
try {
|
|
16
|
+
verdict = await agentCheckCommand({
|
|
17
|
+
commandLine: parsed.command,
|
|
18
|
+
...(parsed.cwd ? { cwd: parsed.cwd } : {}),
|
|
19
|
+
...(deps.env ? { env: deps.env } : {}),
|
|
20
|
+
...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Any failure computing the verdict must fail closed: an unhandled throw
|
|
25
|
+
// would otherwise exit non-zero with no decision, which most agents read
|
|
26
|
+
// as allow.
|
|
27
|
+
verdict = { decision: "deny", reason: `dg hook: the firewall check failed, so the install is blocked. ${ESCALATE}` };
|
|
28
|
+
}
|
|
29
|
+
const emitted = integration.emitDecision(verdict);
|
|
30
|
+
const stderr = allowStderr(verdict);
|
|
31
|
+
return { stdout: emitted.stdout, stderr, exitCode: emitted.exitCode };
|
|
32
|
+
}
|
|
33
|
+
function allowStderr(verdict) {
|
|
34
|
+
if (verdict.decision !== "allow") {
|
|
35
|
+
return ` ${redactSecrets(verdict.reason ?? "DG firewall")}\n`;
|
|
36
|
+
}
|
|
37
|
+
const note = verdict.screened ? formatScreenedNote(verdict.screened) : "";
|
|
38
|
+
return note ? ` ${redactSecrets(note)}\n` : "";
|
|
39
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const PRESERVED_ENTRIES = ["config.toml", "config", "credentials.toml", "credentials"];
|
|
5
|
+
export function userCargoHome(env) {
|
|
6
|
+
const explicit = env.CARGO_HOME?.trim();
|
|
7
|
+
return explicit ? explicit : join(homedir(), ".cargo");
|
|
8
|
+
}
|
|
9
|
+
// Cargo keeps registry config and auth tokens (config.toml / credentials.toml)
|
|
10
|
+
// inside CARGO_HOME, next to the crate cache. dg redirects CARGO_HOME to an empty
|
|
11
|
+
// per-session dir so cached crates re-fetch through the firewall, which would also
|
|
12
|
+
// hide a private-registry user's config and tokens — so link those back in.
|
|
13
|
+
export function prepareCargoHome(cacheDir, source) {
|
|
14
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
15
|
+
const linked = [];
|
|
16
|
+
for (const name of PRESERVED_ENTRIES) {
|
|
17
|
+
const target = join(source, name);
|
|
18
|
+
const link = join(cacheDir, name);
|
|
19
|
+
if (!existsSync(target) || pathPresent(link)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
symlinkSync(target, link);
|
|
24
|
+
linked.push(name);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return linked;
|
|
31
|
+
}
|
|
32
|
+
function pathPresent(path) {
|
|
33
|
+
try {
|
|
34
|
+
lstatSync(path);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import { optionalPackageManagerNames, optionalSupportGate } from "../setup/optional-support.js";
|
|
2
2
|
const supportedManagers = ["npm", "npx", "pnpm", "pnpx", "yarn", "pip", "pipx", "uv", "uvx", "cargo"];
|
|
3
3
|
const gatedManagers = optionalPackageManagerNames();
|
|
4
|
-
const jsProtected = new Set(["install", "i", "ci", "add", "update", "dedupe", "exec"]);
|
|
5
|
-
const pnpmProtected = new Set(["install", "i", "add", "update", "dlx", "exec"]);
|
|
6
|
-
const yarnProtected = new Set(["add", "install", "upgrade", "dlx"]);
|
|
4
|
+
const jsProtected = new Set(["install", "i", "ci", "add", "update", "dedupe", "exec", "create"]);
|
|
5
|
+
const pnpmProtected = new Set(["install", "i", "add", "update", "dlx", "exec", "create"]);
|
|
6
|
+
const yarnProtected = new Set(["add", "install", "upgrade", "dlx", "create"]);
|
|
7
7
|
const pipProtected = new Set(["install", "download", "wheel"]);
|
|
8
8
|
const pipxProtected = new Set(["install", "upgrade", "inject", "run"]);
|
|
9
9
|
const uvProtected = new Set(["add", "sync"]);
|
|
10
10
|
const cargoProtected = new Set(["add", "install", "fetch", "update", "build", "test", "check", "run"]);
|
|
11
11
|
const passthrough = new Set(["help", "--help", "-h", "version", "--version", "-v", "list", "ls", "show", "freeze"]);
|
|
12
|
+
// pip3, pip3.12, python3, python3.11 are the canonical install commands on
|
|
13
|
+
// macOS/Homebrew and many Linux distros (see BINARY_FALLBACKS in
|
|
14
|
+
// resolve-real-binary.ts). The classifier keys on the base name, so a
|
|
15
|
+
// version-suffixed spelling must normalize back to pip/python or it falls
|
|
16
|
+
// through to a silent allow. `pipx`/`pipenv` are left untouched — only a bare
|
|
17
|
+
// numeric/dotted version suffix is stripped.
|
|
18
|
+
export function normalizeManagerName(name) {
|
|
19
|
+
const m = /^(pip|python)(\d+(?:\.\d+)*)?$/.exec(name);
|
|
20
|
+
return m && m[1] ? m[1] : name;
|
|
21
|
+
}
|
|
12
22
|
export function packageManagerNames() {
|
|
13
23
|
return [...supportedManagers, ...gatedManagers];
|
|
14
24
|
}
|
|
15
25
|
export function isSupportedPackageManager(manager) {
|
|
16
26
|
return supportedManagers.includes(manager);
|
|
17
27
|
}
|
|
18
|
-
export function classifyPackageManagerInvocation(
|
|
28
|
+
export function classifyPackageManagerInvocation(rawManager, args) {
|
|
29
|
+
const manager = normalizeManagerName(rawManager);
|
|
19
30
|
if (!isSupportedPackageManager(manager)) {
|
|
20
31
|
return unsupportedClassification(manager, args);
|
|
21
32
|
}
|
|
@@ -29,6 +40,13 @@ export function classifyPackageManagerInvocation(manager, args) {
|
|
|
29
40
|
return classifyByCommand(manager, args, "pnpm", pnpmProtected, "pnpm install/fetch command", "javascript");
|
|
30
41
|
}
|
|
31
42
|
if (manager === "yarn") {
|
|
43
|
+
// A bare `yarn` (no sub-command) runs `yarn install`, fetching every lockfile
|
|
44
|
+
// dependency; classify it protected rather than passthrough. Read-only
|
|
45
|
+
// version/help invocations stay passthrough.
|
|
46
|
+
const readOnlyFlag = args.some((a) => a === "--version" || a === "-v" || a === "--help" || a === "-h");
|
|
47
|
+
if (firstCommand(args) === "" && !readOnlyFlag) {
|
|
48
|
+
return protectedClassification("yarn", args, "yarn", "bare yarn installs all lockfile dependencies");
|
|
49
|
+
}
|
|
32
50
|
return classifyByCommand(manager, args, "yarn", yarnProtected, "Yarn classic install/fetch command", "javascript");
|
|
33
51
|
}
|
|
34
52
|
if (manager === "pip") {
|
|
@@ -44,7 +62,7 @@ export function classifyPackageManagerInvocation(manager, args) {
|
|
|
44
62
|
}
|
|
45
63
|
function classifyByCommand(manager, args, realBinaryName, protectedCommands, protectedReason, ecosystem) {
|
|
46
64
|
const action = firstCommand(args);
|
|
47
|
-
if (protectedCommands.has(action) || containsFetchSpec(args)) {
|
|
65
|
+
if (protectedCommands.has(action) || containsFetchSpec(args) || initFetchesPackage(action, args, protectedCommands)) {
|
|
48
66
|
return {
|
|
49
67
|
manager,
|
|
50
68
|
ecosystem,
|
|
@@ -70,8 +88,13 @@ function classifyUv(args) {
|
|
|
70
88
|
if (action === "pip" && pipProtected.has(args[1] ?? "")) {
|
|
71
89
|
return protectedClassification("uv", args, "uv", "uv pip install/fetch command");
|
|
72
90
|
}
|
|
73
|
-
if (action === "tool" && args[1]
|
|
74
|
-
return protectedClassification("uv", args, "uv", "uv tool run fetches package artifacts");
|
|
91
|
+
if (action === "tool" && ["run", "install", "upgrade"].includes(args[1] ?? "")) {
|
|
92
|
+
return protectedClassification("uv", args, "uv", "uv tool run/install/upgrade fetches package artifacts");
|
|
93
|
+
}
|
|
94
|
+
if (action === "run" && uvRunFetchesPackage(args)) {
|
|
95
|
+
// `uv run --with <pkg> …` fetches <pkg> from PyPI and runs it: a real
|
|
96
|
+
// fetch-and-execute path that the bare `run` verb otherwise waves through.
|
|
97
|
+
return protectedClassification("uv", args, "uv", "uv run --with fetches and runs a package");
|
|
75
98
|
}
|
|
76
99
|
if (uvProtected.has(action) || containsFetchSpec(args)) {
|
|
77
100
|
return protectedClassification("uv", args, "uv", "uv install/fetch command");
|
|
@@ -111,6 +134,20 @@ function unsupportedClassification(manager, args) {
|
|
|
111
134
|
function firstCommand(args) {
|
|
112
135
|
return args.find((arg) => !arg.startsWith("-")) ?? "";
|
|
113
136
|
}
|
|
137
|
+
// `npm/pnpm/yarn init <initializer>` aliases to fetching and running
|
|
138
|
+
// create-<initializer>, the same as the `create` form; bare `init` / `init -y`
|
|
139
|
+
// is a local scaffold with no fetch. Gated to the JS managers (their protected
|
|
140
|
+
// set carries "create"); cargo/pip `init` are local-only.
|
|
141
|
+
function initFetchesPackage(action, args, protectedCommands) {
|
|
142
|
+
if (action !== "init" || !protectedCommands.has("create")) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const commandIndex = args.findIndex((arg) => !arg.startsWith("-"));
|
|
146
|
+
return args.slice(commandIndex + 1).some((arg) => arg !== "--" && !arg.startsWith("-"));
|
|
147
|
+
}
|
|
148
|
+
export function uvRunFetchesPackage(args) {
|
|
149
|
+
return args.some((a) => a === "--with" || a === "--with-editable" || a === "--with-requirements" || a.startsWith("--with="));
|
|
150
|
+
}
|
|
114
151
|
function containsFetchSpec(args) {
|
|
115
152
|
return args.some((arg) => /^https?:\/\//.test(arg) || /^git\+https?:\/\//.test(arg) || /\.t(ar\.)?gz(?:$|[#?])/.test(arg));
|
|
116
153
|
}
|
package/dist/launcher/env.js
CHANGED
|
@@ -1,53 +1,132 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { proxyUrlWithAuth, readProxyAuthToken } from "../proxy/auth.js";
|
|
3
|
+
// The client read timeout must exceed the proxy's per-artifact verdict ceiling
|
|
4
|
+
// (DG_INSTALL_VERDICT_TIMEOUT_MS, default 240s) plus download headroom, so the
|
|
5
|
+
// package manager never gives up on a legitimately-slow verify. Overridable for
|
|
6
|
+
// constrained environments via DG_CLIENT_READ_TIMEOUT_S.
|
|
7
|
+
function clientReadTimeoutSeconds(env) {
|
|
8
|
+
const override = Number(env.DG_CLIENT_READ_TIMEOUT_S);
|
|
9
|
+
if (Number.isFinite(override) && override > 0) {
|
|
10
|
+
return Math.ceil(override);
|
|
11
|
+
}
|
|
12
|
+
const verdictMs = Number(env.DG_INSTALL_VERDICT_TIMEOUT_MS);
|
|
13
|
+
const verdictSeconds = Number.isFinite(verdictMs) && verdictMs > 0 ? verdictMs / 1000 : 240;
|
|
14
|
+
return Math.ceil(verdictSeconds + 60);
|
|
15
|
+
}
|
|
16
|
+
// The wrapped package manager and every lifecycle/postinstall script it runs are
|
|
17
|
+
// untrusted. The dg account credential must never reach them; only the trusted
|
|
18
|
+
// proxy worker (which makes the authenticated API calls) keeps it. Applied on
|
|
19
|
+
// EVERY spawn path, not just the proxy one.
|
|
20
|
+
export function scrubChildSecrets(env) {
|
|
21
|
+
delete env.DG_API_KEY;
|
|
22
|
+
delete env.DG_API_TOKEN;
|
|
23
|
+
return env;
|
|
24
|
+
}
|
|
25
|
+
// The union of proxy-routing vars for an AGENT environment. Unlike a single
|
|
26
|
+
// wrapped install, an agent runs arbitrary package managers, so every manager's
|
|
27
|
+
// proxy + CA var is set at once (npm/node, pip, uv, cargo), plus DG_PROXY_ACTIVE
|
|
28
|
+
// so the install-hook pre-screen defers statically-undecidable commands to the
|
|
29
|
+
// proxy. Every value is a literal (the proxy URL, the CA path) — no shell
|
|
30
|
+
// expansion — so it is safe to drop into an agent's settings `env` block.
|
|
31
|
+
export function buildAgentRoutingEnv(proxyUrl, caPath) {
|
|
32
|
+
const authToken = readProxyAuthToken(dirname(caPath));
|
|
33
|
+
const url = authToken ? proxyUrlWithAuth(proxyUrl, authToken) : proxyUrl;
|
|
34
|
+
const noProxy = "127.0.0.1,localhost";
|
|
35
|
+
return {
|
|
36
|
+
DG_PROXY_ACTIVE: "1",
|
|
37
|
+
NO_PROXY: noProxy,
|
|
38
|
+
no_proxy: noProxy,
|
|
39
|
+
HTTP_PROXY: url,
|
|
40
|
+
HTTPS_PROXY: url,
|
|
41
|
+
http_proxy: url,
|
|
42
|
+
https_proxy: url,
|
|
43
|
+
ALL_PROXY: url,
|
|
44
|
+
npm_config_proxy: url,
|
|
45
|
+
npm_config_https_proxy: url,
|
|
46
|
+
NODE_EXTRA_CA_CERTS: caPath,
|
|
47
|
+
REQUESTS_CA_BUNDLE: caPath,
|
|
48
|
+
PIP_CERT: caPath,
|
|
49
|
+
SSL_CERT_FILE: caPath,
|
|
50
|
+
CARGO_HTTP_CAINFO: caPath,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
1
53
|
export function buildProxyChildEnv(options) {
|
|
2
|
-
const
|
|
54
|
+
const authToken = readProxyAuthToken(dirname(options.caBundlePath));
|
|
55
|
+
const proxyUrl = authToken ? proxyUrlWithAuth(options.proxyUrl, authToken) : options.proxyUrl;
|
|
56
|
+
const env = scrubChildSecrets({
|
|
3
57
|
...options.baseEnv,
|
|
4
58
|
DG_PROXY_ACTIVE: "1"
|
|
5
|
-
};
|
|
6
|
-
|
|
59
|
+
});
|
|
60
|
+
// dg fully controls NO_PROXY (loopback only). An inherited NO_PROXY that names
|
|
61
|
+
// the registry (or a `*`/`.org` glob) would route the manager straight past the
|
|
62
|
+
// firewall, so the inherited value is dropped, not merged.
|
|
63
|
+
const noProxy = (options.noProxyHosts ?? ["127.0.0.1", "localhost"]).join(",");
|
|
64
|
+
env.NO_PROXY = noProxy;
|
|
65
|
+
env.no_proxy = noProxy;
|
|
66
|
+
const readTimeoutSeconds = clientReadTimeoutSeconds(options.baseEnv);
|
|
7
67
|
if (["npm", "npx", "pnpm", "pnpx", "yarn"].includes(options.manager)) {
|
|
8
|
-
env.HTTP_PROXY =
|
|
9
|
-
env.HTTPS_PROXY =
|
|
10
|
-
env.npm_config_proxy =
|
|
11
|
-
env.npm_config_https_proxy =
|
|
12
|
-
env.NO_PROXY = noProxy;
|
|
68
|
+
env.HTTP_PROXY = proxyUrl;
|
|
69
|
+
env.HTTPS_PROXY = proxyUrl;
|
|
70
|
+
env.npm_config_proxy = proxyUrl;
|
|
71
|
+
env.npm_config_https_proxy = proxyUrl;
|
|
13
72
|
env.NODE_EXTRA_CA_CERTS = options.caBundlePath;
|
|
73
|
+
// The proxy withholds every artifact byte until its verdict resolves, so the
|
|
74
|
+
// package manager sees no response data until then. Its own network read
|
|
75
|
+
// timeout must outlast the proxy's worst-case verdict wait or it aborts a
|
|
76
|
+
// legitimate install mid-verify (pip's 15s default is what made large cold
|
|
77
|
+
// installs fail). Set it explicitly so a user-lowered global config can't
|
|
78
|
+
// undercut the proxy.
|
|
79
|
+
env.npm_config_fetch_timeout = String(readTimeoutSeconds * 1000);
|
|
80
|
+
if (options.manager === "yarn") {
|
|
81
|
+
env.YARN_NETWORK_TIMEOUT = String(readTimeoutSeconds * 1000);
|
|
82
|
+
}
|
|
83
|
+
if (options.manager === "yarn") {
|
|
84
|
+
// Yarn Berry (2+) ignores HTTP(S)_PROXY/npm_config_*; without its own
|
|
85
|
+
// proxy + CA config it fetches straight from the registry, bypassing the
|
|
86
|
+
// firewall. Classic Yarn ignores these YARN_ vars, so set them for both.
|
|
87
|
+
env.YARN_HTTP_PROXY = proxyUrl;
|
|
88
|
+
env.YARN_HTTPS_PROXY = proxyUrl;
|
|
89
|
+
env.YARN_HTTPS_CA_FILE_PATH = options.caBundlePath;
|
|
90
|
+
}
|
|
14
91
|
if (options.cacheDir) {
|
|
15
92
|
env.npm_config_cache = options.cacheDir;
|
|
16
93
|
if (options.manager === "yarn") {
|
|
94
|
+
// Both: YARN_CACHE_FOLDER is Berry's per-project cache, but Berry 2+
|
|
95
|
+
// defaults to a shared global cache (~/.yarn/berry) that a warm install
|
|
96
|
+
// serves from without ever fetching, so redirect that too.
|
|
17
97
|
env.YARN_CACHE_FOLDER = options.cacheDir;
|
|
98
|
+
env.YARN_GLOBAL_FOLDER = options.cacheDir;
|
|
18
99
|
}
|
|
19
100
|
}
|
|
20
101
|
return env;
|
|
21
102
|
}
|
|
22
103
|
if (options.manager === "pip" || options.manager === "pipx") {
|
|
23
|
-
env.http_proxy =
|
|
24
|
-
env.https_proxy =
|
|
25
|
-
env.no_proxy = noProxy;
|
|
104
|
+
env.http_proxy = proxyUrl;
|
|
105
|
+
env.https_proxy = proxyUrl;
|
|
26
106
|
env.REQUESTS_CA_BUNDLE = options.caBundlePath;
|
|
27
107
|
env.PIP_CERT = options.caBundlePath;
|
|
28
108
|
env.PIP_NO_CACHE_DIR = "1";
|
|
109
|
+
env.PIP_DEFAULT_TIMEOUT = String(readTimeoutSeconds);
|
|
29
110
|
return env;
|
|
30
111
|
}
|
|
31
112
|
if (options.manager === "uv" || options.manager === "uvx") {
|
|
32
|
-
env.HTTP_PROXY =
|
|
33
|
-
env.HTTPS_PROXY =
|
|
34
|
-
env.ALL_PROXY =
|
|
35
|
-
env.NO_PROXY = noProxy;
|
|
113
|
+
env.HTTP_PROXY = proxyUrl;
|
|
114
|
+
env.HTTPS_PROXY = proxyUrl;
|
|
115
|
+
env.ALL_PROXY = proxyUrl;
|
|
36
116
|
env.SSL_CERT_FILE = options.caBundlePath;
|
|
37
117
|
env.UV_NO_CACHE = "1";
|
|
118
|
+
env.UV_HTTP_TIMEOUT = String(readTimeoutSeconds);
|
|
38
119
|
return env;
|
|
39
120
|
}
|
|
40
|
-
env.HTTPS_PROXY =
|
|
41
|
-
env.https_proxy =
|
|
42
|
-
env.http_proxy =
|
|
43
|
-
env.NO_PROXY = noProxy;
|
|
121
|
+
env.HTTPS_PROXY = proxyUrl;
|
|
122
|
+
env.https_proxy = proxyUrl;
|
|
123
|
+
env.http_proxy = proxyUrl;
|
|
44
124
|
env.CARGO_HTTP_CAINFO = options.caBundlePath;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
values.add(host);
|
|
125
|
+
env.CARGO_HTTP_TIMEOUT = String(readTimeoutSeconds);
|
|
126
|
+
if (options.cacheDir) {
|
|
127
|
+
// cargo serves a cached crate from ~/.cargo with zero network, skipping the
|
|
128
|
+
// firewall; pointing CARGO_HOME at the throwaway dir forces a re-fetch.
|
|
129
|
+
env.CARGO_HOME = options.cacheDir;
|
|
51
130
|
}
|
|
52
|
-
return
|
|
131
|
+
return env;
|
|
53
132
|
}
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { analyzePackages } from "../api/analyze.js";
|
|
4
|
-
import { loadUserConfig } from "../config/settings.js";
|
|
4
|
+
import { DEFAULT_CONFIG, loadUserConfig, trustsProjectOverrides } from "../config/settings.js";
|
|
5
|
+
import { honoredOverrides } from "../project/override-trust.js";
|
|
5
6
|
import { matchDecision, packageKey } from "../decisions/apply.js";
|
|
6
7
|
import { offerRememberOnIo } from "../decisions/remember-prompt.js";
|
|
7
8
|
import { provenanceDowngradeLine } from "../presentation/provenance.js";
|
|
8
9
|
import { defaultPromptIo } from "../install-ui/prompt.js";
|
|
9
|
-
import { cooldownRequestParam, formatCooldownDuration, formatPackageAge, isCooldownExempt } from "../policy/cooldown.js";
|
|
10
|
+
import { cooldownRequestParam, formatCooldownDuration, formatPackageAge, isCooldownExempt, isCooldownExemptByDgFile } from "../policy/cooldown.js";
|
|
10
11
|
import { findProjectRoot, loadDgFile, resolveAcceptedBy, warnUnreadableDgFile } from "../project/dgfile.js";
|
|
11
12
|
import { parsePipReportInstallCount, parsePipReportInstallSet } from "./pip-report.js";
|
|
12
|
-
|
|
13
|
+
import { resolveSpawnInvocation } from "./spawn-invocation.js";
|
|
14
|
+
export function resolvePreflightCooldown(env, ecosystem) {
|
|
15
|
+
// A corrupt user config must not silently disable the cooldown gate; fall back
|
|
16
|
+
// to defaults so any env-configured cooldown is still applied.
|
|
17
|
+
let config;
|
|
13
18
|
try {
|
|
14
|
-
|
|
15
|
-
const param = cooldownRequestParam(config, env, "pypi", "");
|
|
16
|
-
return param ? { param, exempt: config.cooldown.exempt } : undefined;
|
|
19
|
+
config = loadUserConfig(env);
|
|
17
20
|
}
|
|
18
21
|
catch {
|
|
19
|
-
|
|
22
|
+
config = DEFAULT_CONFIG;
|
|
20
23
|
}
|
|
24
|
+
const param = cooldownRequestParam(config, env, ecosystem, "");
|
|
25
|
+
return param ? { param, exempt: config.cooldown.exempt, ecosystem } : undefined;
|
|
21
26
|
}
|
|
22
|
-
function isQuarantined(pkg, context) {
|
|
27
|
+
function isQuarantined(pkg, context, dgExemptions = []) {
|
|
23
28
|
if (!context || !pkg.cooldown) {
|
|
24
29
|
return false;
|
|
25
30
|
}
|
|
26
|
-
if (isCooldownExempt(pkg.name, context.exempt,
|
|
31
|
+
if (isCooldownExempt(pkg.name, context.exempt, context.ecosystem)
|
|
32
|
+
|| isCooldownExemptByDgFile(pkg.name, context.ecosystem, dgExemptions)) {
|
|
27
33
|
return false;
|
|
28
34
|
}
|
|
29
35
|
return pkg.cooldown.status === "quarantine"
|
|
@@ -39,7 +45,12 @@ export function resolvePreflightDecisions(ecosystem, cwd, env = process.env) {
|
|
|
39
45
|
if (!file.readable) {
|
|
40
46
|
return null;
|
|
41
47
|
}
|
|
42
|
-
|
|
48
|
+
const honored = honoredOverrides(file, root, env, trustsProjectOverrides(env));
|
|
49
|
+
if (honored.droppedExemptions > 0 || honored.droppedDecisions > 0) {
|
|
50
|
+
process.stderr.write(`dg: ignoring ${honored.droppedExemptions} cooldown exemption(s) and ${honored.droppedDecisions} decision(s) in ${file.path} not authored on this machine — re-add with 'dg cooldown add' / 'dg decisions', or 'dg config set policy.trustProjectAllowlists true' to trust this repo\n`);
|
|
51
|
+
}
|
|
52
|
+
const gatedFile = { ...file, cooldownExemptions: honored.exemptions, decisions: honored.decisions };
|
|
53
|
+
return { root, file: gatedFile, ecosystem, env };
|
|
43
54
|
}
|
|
44
55
|
const PROCEED = { proceed: true };
|
|
45
56
|
const UNRESOLVED = { set: undefined, count: undefined };
|
|
@@ -70,6 +81,15 @@ export function cachedPipResolution(binary, args) {
|
|
|
70
81
|
function pipResolutionKey(binary, args) {
|
|
71
82
|
return JSON.stringify([binary, ...args]);
|
|
72
83
|
}
|
|
84
|
+
// pip's dry-run resolve downloads sdists/wheels it cannot get PEP 658
|
|
85
|
+
// metadata for, so big dependency trees legitimately take minutes on a cold
|
|
86
|
+
// pip cache. A short timeout here silently skips the preflight (and with it
|
|
87
|
+
// the batched analyze + preverified handoff), pushing every package onto the
|
|
88
|
+
// slow per-artifact verdict path.
|
|
89
|
+
function pipResolveTimeoutMs(env) {
|
|
90
|
+
const raw = Number(env.DG_PIP_RESOLVE_TIMEOUT_MS);
|
|
91
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 120_000;
|
|
92
|
+
}
|
|
73
93
|
function resolvePipInstallSet(binary, args, env) {
|
|
74
94
|
if (!binary)
|
|
75
95
|
return Promise.resolve(UNRESOLVED);
|
|
@@ -87,9 +107,11 @@ function resolvePipInstallSet(binary, args, env) {
|
|
|
87
107
|
};
|
|
88
108
|
let child;
|
|
89
109
|
try {
|
|
90
|
-
|
|
110
|
+
const invocation = resolveSpawnInvocation(binary, [...args, "--dry-run", "--report", "-", "--quiet"]);
|
|
111
|
+
child = spawn(invocation.command, [...invocation.args], {
|
|
91
112
|
env,
|
|
92
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
113
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
114
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
93
115
|
});
|
|
94
116
|
}
|
|
95
117
|
catch {
|
|
@@ -102,7 +124,7 @@ function resolvePipInstallSet(binary, args, env) {
|
|
|
102
124
|
}
|
|
103
125
|
catch { /* already exited */ }
|
|
104
126
|
finish(UNRESOLVED);
|
|
105
|
-
},
|
|
127
|
+
}, pipResolveTimeoutMs(env));
|
|
106
128
|
child.stdout?.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
|
|
107
129
|
child.on("error", () => finish(UNRESOLVED));
|
|
108
130
|
child.on("close", (code) => finish(code === 0
|
|
@@ -154,7 +176,7 @@ export function renderProvenanceDowngrades(packages, out) {
|
|
|
154
176
|
out.write(` ⚠ ${pkg.name}@${pkg.version}: ${line} (display only, verdict unchanged)\n`);
|
|
155
177
|
}
|
|
156
178
|
}
|
|
157
|
-
export async function runInstallPreflight(manager, binary, childArgs, env, cwd = process.cwd()) {
|
|
179
|
+
export async function runInstallPreflight(manager, binary, childArgs, env, cwd = process.cwd(), beforeInteraction) {
|
|
158
180
|
if (manager !== "pip" || !binary) {
|
|
159
181
|
return PROCEED;
|
|
160
182
|
}
|
|
@@ -165,18 +187,42 @@ export async function runInstallPreflight(manager, binary, childArgs, env, cwd =
|
|
|
165
187
|
pipResolutions.set(key, resolution);
|
|
166
188
|
}
|
|
167
189
|
const set = resolution.set;
|
|
168
|
-
if (!set
|
|
190
|
+
if (!set) {
|
|
191
|
+
process.stderr.write(" dg: could not resolve the install set ahead of time — verifying during install instead\n");
|
|
192
|
+
return PROCEED;
|
|
193
|
+
}
|
|
194
|
+
if (set.length === 0) {
|
|
169
195
|
return PROCEED;
|
|
170
196
|
}
|
|
171
|
-
const cooldownContext = resolvePreflightCooldown(env);
|
|
197
|
+
const cooldownContext = resolvePreflightCooldown(env, "pypi");
|
|
172
198
|
let verdicts;
|
|
173
199
|
try {
|
|
174
200
|
verdicts = await analyzePackages(set.map((pkg) => ({ name: pkg.name, version: pkg.version })), { ecosystem: "pypi", env, ...(cooldownContext ? { cooldown: cooldownContext.param } : {}) });
|
|
175
201
|
}
|
|
176
202
|
catch {
|
|
203
|
+
process.stderr.write(" dg: pre-install check unavailable — verifying during install instead\n");
|
|
177
204
|
return PROCEED;
|
|
178
205
|
}
|
|
179
|
-
|
|
206
|
+
beforeInteraction?.();
|
|
207
|
+
const decision = await decideFromVerdicts(verdicts.packages, defaultPromptIo(), cooldownContext, resolvePreflightDecisions("pypi", cwd, env));
|
|
208
|
+
if (!decision.proceed || decision.forceOverride) {
|
|
209
|
+
return decision;
|
|
210
|
+
}
|
|
211
|
+
const preverified = preverifiedEntries(verdicts.packages, cooldownContext !== undefined);
|
|
212
|
+
return preverified.length > 0 ? { ...decision, preverified } : decision;
|
|
213
|
+
}
|
|
214
|
+
export function preverifiedEntries(packages, cooldownRequested) {
|
|
215
|
+
return packages
|
|
216
|
+
.filter((pkg) => pkg.action === "pass" || pkg.action === "warn")
|
|
217
|
+
.map((pkg) => ({
|
|
218
|
+
ecosystem: "pypi",
|
|
219
|
+
name: pkg.name,
|
|
220
|
+
version: pkg.version,
|
|
221
|
+
action: pkg.action,
|
|
222
|
+
...(pkg.reasons[0] ? { reason: pkg.reasons[0] } : {}),
|
|
223
|
+
...(typeof pkg.artifactSha256 === "string" && pkg.artifactSha256.length > 0 ? { scannedSha256: pkg.artifactSha256 } : {}),
|
|
224
|
+
cooldownEvaluated: cooldownRequested && pkg.cooldown !== undefined
|
|
225
|
+
}));
|
|
180
226
|
}
|
|
181
227
|
export async function decideFromVerdicts(packages, io, cooldownContext, decisions = null) {
|
|
182
228
|
if (io.isTTY) {
|
|
@@ -185,7 +231,7 @@ export async function decideFromVerdicts(packages, io, cooldownContext, decision
|
|
|
185
231
|
const covered = [];
|
|
186
232
|
const entries = packages
|
|
187
233
|
.map((pkg) => {
|
|
188
|
-
const viaCooldown = isQuarantined(pkg, cooldownContext) && pkg.action !== "block";
|
|
234
|
+
const viaCooldown = isQuarantined(pkg, cooldownContext, decisions?.file.cooldownExemptions ?? []) && pkg.action !== "block";
|
|
189
235
|
const action = viaCooldown ? "block" : pkg.action ?? "pass";
|
|
190
236
|
return { pkg, action, viaCooldown };
|
|
191
237
|
})
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { createLaunchPlan, runWithProductionProxyLive, EXIT_INSTALL_BLOCKED } from "./run.js";
|
|
1
|
+
import { createLaunchPlan, prepareProxyWorker, runWithProductionProxyLive, EXIT_INSTALL_BLOCKED } from "./run.js";
|
|
2
2
|
import { runInstallPreflight } from "./install-preflight.js";
|
|
3
|
-
import { isSupportedPackageManager } from "./classify.js";
|
|
3
|
+
import { isSupportedPackageManager, normalizeManagerName } from "./classify.js";
|
|
4
4
|
import { isCiEnv, resolvePresentation } from "../presentation/mode.js";
|
|
5
|
+
import { startPrepSpinner } from "../install-ui/prep-spinner.js";
|
|
5
6
|
const FALL_THROUGH = { handled: false };
|
|
6
7
|
export async function maybeRunLiveInstall(args, options = {}) {
|
|
7
8
|
const env = options.env ?? process.env;
|
|
8
9
|
if (!process.stdout.isTTY || isCiEnv(env) || resolvePresentation().mode !== "rich") {
|
|
9
10
|
return FALL_THROUGH;
|
|
10
11
|
}
|
|
11
|
-
const [
|
|
12
|
-
|
|
12
|
+
const [rawManager, ...rest] = args;
|
|
13
|
+
const manager = normalizeManagerName(rawManager ?? "");
|
|
14
|
+
if (!rawManager || !isSupportedPackageManager(manager)) {
|
|
13
15
|
return FALL_THROUGH;
|
|
14
16
|
}
|
|
15
17
|
const { childArgs, forceOverride } = stripControlArgs(rest);
|
|
@@ -17,24 +19,43 @@ export async function maybeRunLiveInstall(args, options = {}) {
|
|
|
17
19
|
if (plan.classification.kind !== "protected" || !plan.realBinary.path) {
|
|
18
20
|
return FALL_THROUGH;
|
|
19
21
|
}
|
|
22
|
+
const appLoading = import("../install-ui/live-install-app.js");
|
|
23
|
+
appLoading.catch(() => undefined);
|
|
24
|
+
let prepared = forceOverride ? undefined : prepareProxyWorker(plan.classification, env);
|
|
20
25
|
let effectiveOverride = forceOverride;
|
|
26
|
+
let preverified;
|
|
21
27
|
if (!forceOverride) {
|
|
22
|
-
const
|
|
28
|
+
const spinner = startPrepSpinner("DG preparing…");
|
|
29
|
+
let preflight;
|
|
30
|
+
try {
|
|
31
|
+
preflight = await runInstallPreflight(manager, plan.realBinary.path, childArgs, env, process.cwd(), spinner.stop);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
spinner.stop();
|
|
35
|
+
}
|
|
23
36
|
if (!preflight.proceed) {
|
|
37
|
+
prepared?.discard();
|
|
24
38
|
return { handled: true, result: { exitCode: EXIT_INSTALL_BLOCKED, stdout: "", stderr: " Install cancelled.\n" } };
|
|
25
39
|
}
|
|
26
40
|
effectiveOverride = preflight.forceOverride;
|
|
41
|
+
preverified = preflight.preverified;
|
|
42
|
+
if (effectiveOverride) {
|
|
43
|
+
prepared?.discard();
|
|
44
|
+
prepared = undefined;
|
|
45
|
+
}
|
|
27
46
|
}
|
|
28
47
|
const runOptions = {
|
|
29
48
|
env,
|
|
30
|
-
...(effectiveOverride ? { forceOverride: effectiveOverride } : {})
|
|
49
|
+
...(effectiveOverride ? { forceOverride: effectiveOverride } : {}),
|
|
50
|
+
...(preverified && preverified.length > 0 && !effectiveOverride ? { preverified } : {})
|
|
31
51
|
};
|
|
32
|
-
const { renderLiveInstall } = await import("../install-ui/live-install-app.js");
|
|
33
52
|
try {
|
|
34
|
-
const
|
|
53
|
+
const { renderLiveInstall } = await appLoading;
|
|
54
|
+
const result = await renderLiveInstall((onView) => runWithProductionProxyLive(plan, childArgs, runOptions, onView, prepared));
|
|
35
55
|
return { handled: true, result };
|
|
36
56
|
}
|
|
37
57
|
catch (error) {
|
|
58
|
+
prepared?.discard();
|
|
38
59
|
return {
|
|
39
60
|
handled: true,
|
|
40
61
|
result: {
|