@westbayberry/dg 1.3.2 → 2.0.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.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +357 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +83 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +909 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1166 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +899 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54141
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. package/dist/python-hook/dg_pip_hook.py +0 -130
@@ -0,0 +1,302 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { dirname, join } from "node:path";
4
+ import { resolveDgPaths } from "../state/index.js";
5
+ export const CONFIG_KEYS = Object.freeze([
6
+ "api.baseUrl",
7
+ "org.id",
8
+ "policy.mode",
9
+ "policy.trustProjectAllowlists",
10
+ "policy.allowForceOverride",
11
+ "policy.scriptHardening",
12
+ "gitHook.onWarn",
13
+ "gitHook.onIncomplete",
14
+ "audit.upload",
15
+ "telemetry.enabled",
16
+ "webhooks.enabled"
17
+ ]);
18
+ export const DEFAULT_CONFIG = Object.freeze({
19
+ version: 1,
20
+ api: {
21
+ baseUrl: "https://api.westbayberry.com"
22
+ },
23
+ org: {
24
+ id: ""
25
+ },
26
+ policy: {
27
+ mode: "block",
28
+ trustProjectAllowlists: false,
29
+ allowForceOverride: true,
30
+ scriptHardening: false
31
+ },
32
+ gitHook: {
33
+ onWarn: "prompt",
34
+ onIncomplete: "allow"
35
+ },
36
+ audit: {
37
+ upload: false
38
+ },
39
+ telemetry: {
40
+ enabled: false
41
+ },
42
+ webhooks: {
43
+ enabled: true
44
+ }
45
+ });
46
+ export class ConfigError extends Error {
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "ConfigError";
50
+ }
51
+ }
52
+ export function userConfigPath(paths) {
53
+ return join(paths.configDir, "config.json");
54
+ }
55
+ export function loadUserConfig(env = process.env) {
56
+ const paths = resolveDgPaths(env);
57
+ const path = userConfigPath(paths);
58
+ if (!existsSync(path)) {
59
+ return DEFAULT_CONFIG;
60
+ }
61
+ try {
62
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
63
+ return normalizeConfig(parsed);
64
+ }
65
+ catch (error) {
66
+ throw new ConfigError(`Malformed dg config at ${path}: ${error instanceof Error ? error.message : "unknown error"}`);
67
+ }
68
+ }
69
+ export function saveUserConfig(config, env = process.env) {
70
+ const paths = resolveDgPaths(env);
71
+ writeJsonAtomic(userConfigPath(paths), config);
72
+ }
73
+ export function getConfigValue(config, key) {
74
+ if (key === "api.baseUrl") {
75
+ return config.api.baseUrl;
76
+ }
77
+ if (key === "org.id") {
78
+ return config.org.id;
79
+ }
80
+ if (key === "policy.mode") {
81
+ return config.policy.mode;
82
+ }
83
+ if (key === "policy.trustProjectAllowlists") {
84
+ return String(config.policy.trustProjectAllowlists);
85
+ }
86
+ if (key === "policy.allowForceOverride") {
87
+ return String(config.policy.allowForceOverride);
88
+ }
89
+ if (key === "policy.scriptHardening") {
90
+ return String(config.policy.scriptHardening);
91
+ }
92
+ if (key === "gitHook.onWarn") {
93
+ return config.gitHook.onWarn;
94
+ }
95
+ if (key === "gitHook.onIncomplete") {
96
+ return config.gitHook.onIncomplete;
97
+ }
98
+ if (key === "audit.upload") {
99
+ return String(config.audit.upload);
100
+ }
101
+ if (key === "telemetry.enabled") {
102
+ return String(config.telemetry.enabled);
103
+ }
104
+ return String(config.webhooks.enabled);
105
+ }
106
+ export function listConfig(config) {
107
+ return CONFIG_KEYS.map((key) => ({
108
+ key,
109
+ value: getConfigValue(config, key)
110
+ }));
111
+ }
112
+ export function setConfigValue(config, key, rawValue) {
113
+ if (key === "api.baseUrl") {
114
+ return {
115
+ ...config,
116
+ api: {
117
+ baseUrl: parseUrl(rawValue)
118
+ }
119
+ };
120
+ }
121
+ if (key === "org.id") {
122
+ return {
123
+ ...config,
124
+ org: {
125
+ id: rawValue.trim()
126
+ }
127
+ };
128
+ }
129
+ if (key === "policy.mode") {
130
+ return {
131
+ ...config,
132
+ policy: {
133
+ ...config.policy,
134
+ mode: parsePolicyMode(rawValue)
135
+ }
136
+ };
137
+ }
138
+ if (key === "policy.trustProjectAllowlists") {
139
+ return withPolicyBoolean(config, "trustProjectAllowlists", rawValue);
140
+ }
141
+ if (key === "policy.allowForceOverride") {
142
+ return withPolicyBoolean(config, "allowForceOverride", rawValue);
143
+ }
144
+ if (key === "policy.scriptHardening") {
145
+ return withPolicyBoolean(config, "scriptHardening", rawValue);
146
+ }
147
+ if (key === "gitHook.onWarn") {
148
+ return {
149
+ ...config,
150
+ gitHook: {
151
+ ...config.gitHook,
152
+ onWarn: parseOnWarn(rawValue)
153
+ }
154
+ };
155
+ }
156
+ if (key === "gitHook.onIncomplete") {
157
+ return {
158
+ ...config,
159
+ gitHook: {
160
+ ...config.gitHook,
161
+ onIncomplete: parseOnIncomplete(rawValue)
162
+ }
163
+ };
164
+ }
165
+ if (key === "audit.upload") {
166
+ return {
167
+ ...config,
168
+ audit: {
169
+ upload: parseBoolean(rawValue)
170
+ }
171
+ };
172
+ }
173
+ if (key === "telemetry.enabled") {
174
+ return {
175
+ ...config,
176
+ telemetry: {
177
+ enabled: parseBoolean(rawValue)
178
+ }
179
+ };
180
+ }
181
+ return {
182
+ ...config,
183
+ webhooks: {
184
+ enabled: parseBoolean(rawValue)
185
+ }
186
+ };
187
+ }
188
+ export function unsetConfigValue(config, key) {
189
+ return setConfigValue(config, key, getConfigValue(DEFAULT_CONFIG, key));
190
+ }
191
+ export function isConfigKey(value) {
192
+ return CONFIG_KEYS.includes(value);
193
+ }
194
+ function normalizeConfig(raw) {
195
+ if (raw.version !== undefined && raw.version !== 1) {
196
+ throw new ConfigError("unsupported config version");
197
+ }
198
+ return {
199
+ version: 1,
200
+ api: {
201
+ baseUrl: parseUrl(raw.api?.baseUrl ?? DEFAULT_CONFIG.api.baseUrl)
202
+ },
203
+ org: {
204
+ id: raw.org?.id ?? DEFAULT_CONFIG.org.id
205
+ },
206
+ policy: {
207
+ mode: parsePolicyMode(raw.policy?.mode ?? DEFAULT_CONFIG.policy.mode),
208
+ trustProjectAllowlists: raw.policy?.trustProjectAllowlists ?? DEFAULT_CONFIG.policy.trustProjectAllowlists,
209
+ allowForceOverride: raw.policy?.allowForceOverride ?? DEFAULT_CONFIG.policy.allowForceOverride,
210
+ scriptHardening: raw.policy?.scriptHardening ?? DEFAULT_CONFIG.policy.scriptHardening
211
+ },
212
+ gitHook: {
213
+ onWarn: parseOnWarn(raw.gitHook?.onWarn ?? DEFAULT_CONFIG.gitHook.onWarn),
214
+ onIncomplete: parseOnIncomplete(raw.gitHook?.onIncomplete ?? DEFAULT_CONFIG.gitHook.onIncomplete)
215
+ },
216
+ audit: {
217
+ upload: raw.audit?.upload ?? DEFAULT_CONFIG.audit.upload
218
+ },
219
+ telemetry: {
220
+ enabled: raw.telemetry?.enabled ?? DEFAULT_CONFIG.telemetry.enabled
221
+ },
222
+ webhooks: {
223
+ enabled: raw.webhooks?.enabled ?? DEFAULT_CONFIG.webhooks.enabled
224
+ }
225
+ };
226
+ }
227
+ function withPolicyBoolean(config, key, rawValue) {
228
+ if (key === "mode") {
229
+ return config;
230
+ }
231
+ return {
232
+ ...config,
233
+ policy: {
234
+ ...config.policy,
235
+ [key]: parseBoolean(rawValue)
236
+ }
237
+ };
238
+ }
239
+ function parsePolicyMode(value) {
240
+ if (value === "off" || value === "warn" || value === "block" || value === "strict") {
241
+ return value;
242
+ }
243
+ throw new ConfigError("policy.mode must be one of: off, warn, block, strict");
244
+ }
245
+ function parseOnWarn(value) {
246
+ if (value === "prompt" || value === "allow" || value === "block") {
247
+ return value;
248
+ }
249
+ throw new ConfigError("gitHook.onWarn must be one of: prompt, allow, block");
250
+ }
251
+ function parseOnIncomplete(value) {
252
+ if (value === "allow" || value === "block") {
253
+ return value;
254
+ }
255
+ throw new ConfigError("gitHook.onIncomplete must be one of: allow, block");
256
+ }
257
+ function parseBoolean(value) {
258
+ if (value === "true") {
259
+ return true;
260
+ }
261
+ if (value === "false") {
262
+ return false;
263
+ }
264
+ throw new ConfigError("boolean config values must be true or false");
265
+ }
266
+ function parseUrl(value) {
267
+ const trimmed = value.trim();
268
+ try {
269
+ const url = new URL(trimmed);
270
+ if (url.protocol !== "https:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
271
+ throw new ConfigError("api.baseUrl must use https unless it targets localhost");
272
+ }
273
+ return url.toString().replace(/\/$/, "");
274
+ }
275
+ catch (error) {
276
+ if (error instanceof ConfigError) {
277
+ throw error;
278
+ }
279
+ throw new ConfigError("api.baseUrl must be an absolute URL");
280
+ }
281
+ }
282
+ function writeJsonAtomic(path, value) {
283
+ mkdirSync(dirname(path), {
284
+ recursive: true,
285
+ mode: 0o700
286
+ });
287
+ const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
288
+ try {
289
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
290
+ encoding: "utf8",
291
+ flag: "wx",
292
+ mode: 0o600
293
+ });
294
+ renameSync(tempPath, path);
295
+ }
296
+ catch (error) {
297
+ rmSync(tempPath, {
298
+ force: true
299
+ });
300
+ throw error;
301
+ }
302
+ }
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import InkSpinner from "ink-spinner";
4
+ function packageCount(n) {
5
+ return n === 1 ? "1 package" : `${n} packages`;
6
+ }
7
+ function rule() {
8
+ const width = Math.max(20, Math.min((process.stdout.columns ?? 80) - 4, 56));
9
+ return "─".repeat(width);
10
+ }
11
+ export const LiveInstall = ({ view }) => {
12
+ if (view.total === 0 && !view.blocked) {
13
+ return null;
14
+ }
15
+ if (view.phase === "scanning") {
16
+ return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "cyan", children: _jsx(InkSpinner, { type: "dots" }) }), _jsxs(Text, { children: [" DG verifying ", packageCount(view.total), "\u2026"] }), view.current ? _jsxs(Text, { dimColor: true, children: [" ", view.current] }) : null] }));
17
+ }
18
+ if (view.blocked) {
19
+ const blocked = view.blocked;
20
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "gray", children: rule() }), blocked.kind === "blocked" ? (_jsxs(Text, { color: "red", children: ["\u2718 DG blocked install \u2014 ", blocked.headline] })) : (_jsxs(Text, { color: "yellow", children: ["? DG could not verify ", blocked.packageName, " \u2014 ", blocked.headline] })), _jsxs(Text, { children: [" ", blocked.packageName, " ", blocked.reason] }), blocked.override ? _jsxs(Text, { dimColor: true, children: [" ", "Override: ", blocked.override] }) : null, blocked.nextStep ? _jsxs(Text, { dimColor: true, children: [" ", "Next: ", blocked.nextStep] }) : null] }));
21
+ }
22
+ const total = view.verified + view.flagged;
23
+ return (_jsx(Box, { paddingX: 1, children: view.flagged > 0 ? (_jsxs(Text, { color: "yellow", children: ["\u26A0 DG verified ", packageCount(total), " \u2014 ", view.flagged, " flagged"] })) : (_jsxs(Text, { color: "green", children: ["\u2713 DG verified ", packageCount(total), " \u2014 clean"] })) }));
24
+ };
@@ -0,0 +1,83 @@
1
+ const VERIFIED_BAD = new Set([
2
+ "malware",
3
+ "policy",
4
+ "license",
5
+ "hash-mismatch",
6
+ "private-upload-disabled"
7
+ ]);
8
+ const HEADLINES = {
9
+ pass: "clean",
10
+ warn: "flagged",
11
+ malware: "confirmed malware",
12
+ policy: "blocked by policy",
13
+ license: "license policy",
14
+ "hash-mismatch": "artifact integrity mismatch",
15
+ "private-upload-disabled": "private artifact not scanned",
16
+ "api-unavailable": "scanner unavailable",
17
+ "quota-exceeded": "monthly scan limit reached",
18
+ "api-timeout": "scanner timed out",
19
+ "registry-timeout": "registry timed out",
20
+ "analysis-incomplete": "analysis incomplete",
21
+ "unsupported-manager": "unsupported package manager",
22
+ "proxy-setup-failure": "protection unavailable"
23
+ };
24
+ const NEXT_STEP = {
25
+ malware: "Do not install. Remove the dependency or pin a known-safe version.",
26
+ policy: "Adjust the dependency to satisfy policy, or ask your admin.",
27
+ license: "Replace the dependency or update your license policy.",
28
+ "hash-mismatch": "Clear your package cache and retry. If it persists, do not install.",
29
+ "private-upload-disabled": "Enable private artifact scanning to verify this package.",
30
+ "quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing."
31
+ };
32
+ export function describeBlockedInstall(decision) {
33
+ const verifiedBad = VERIFIED_BAD.has(decision.cause);
34
+ const override = decision.forceOverride && !decision.forceOverride.allowed
35
+ ? "not allowed by your policy"
36
+ : "re-run with --dg-force-install";
37
+ const nextStep = verifiedBad
38
+ ? NEXT_STEP[decision.cause]
39
+ : "Re-check later with 'dg verify', or override if you accept the risk.";
40
+ return {
41
+ kind: verifiedBad ? "blocked" : "unverified",
42
+ packageName: decision.packageName,
43
+ headline: HEADLINES[decision.cause],
44
+ reason: decision.reason,
45
+ ...(nextStep ? { nextStep } : {}),
46
+ ...(verifiedBad ? {} : { override })
47
+ };
48
+ }
49
+ export function renderInstallDecision(decision) {
50
+ if (decision.action === "pass") {
51
+ return `✓ DG verified ${decision.packageName} — clean\n`;
52
+ }
53
+ if (decision.action === "warn") {
54
+ if (decision.forceOverride?.allowed) {
55
+ return `⚠ DG override — installing ${decision.packageName} despite block (--dg-force-install)\n`;
56
+ }
57
+ return `⚠ DG flagged ${decision.packageName} (warn) — ${decision.reason}\n`;
58
+ }
59
+ const verifiedBad = VERIFIED_BAD.has(decision.cause);
60
+ const headline = HEADLINES[decision.cause];
61
+ const lines = [
62
+ verifiedBad
63
+ ? `✘ DG blocked install — ${headline}`
64
+ : `? DG could not verify ${decision.packageName} — ${headline}`,
65
+ ` ${decision.packageName} ${decision.reason}`
66
+ ];
67
+ if (decision.dashboardUrl) {
68
+ lines.push(` Evidence: ${decision.dashboardUrl}`);
69
+ }
70
+ if (decision.unauthenticated) {
71
+ lines.push(" Auth: local policy only (run 'dg login' for full coverage)");
72
+ }
73
+ lines.push(decision.forceOverride && !decision.forceOverride.allowed
74
+ ? " Override: not allowed by your policy"
75
+ : " Override: re-run with --dg-force-install");
76
+ const next = verifiedBad
77
+ ? NEXT_STEP[decision.cause]
78
+ : "Re-check later with 'dg verify', or override if you accept the risk.";
79
+ if (next) {
80
+ lines.push(` Next: ${next}`);
81
+ }
82
+ return `${lines.join("\n")}\n`;
83
+ }
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ import { render, useApp } from "ink";
3
+ import { LiveInstall } from "./LiveInstall.js";
4
+ function createViewStore(initial) {
5
+ let current = initial;
6
+ const listeners = new Set();
7
+ return {
8
+ get: () => current,
9
+ set: (view) => {
10
+ current = view;
11
+ for (const listener of listeners) {
12
+ listener();
13
+ }
14
+ },
15
+ subscribe: (listener) => {
16
+ listeners.add(listener);
17
+ return () => {
18
+ listeners.delete(listener);
19
+ };
20
+ }
21
+ };
22
+ }
23
+ const App = ({ store }) => {
24
+ const view = React.useSyncExternalStore(store.subscribe, store.get, store.get);
25
+ const { exit } = useApp();
26
+ React.useEffect(() => {
27
+ if (view.phase !== "done") {
28
+ return undefined;
29
+ }
30
+ const timer = setTimeout(() => exit(), 30);
31
+ return () => clearTimeout(timer);
32
+ }, [view.phase, exit]);
33
+ return React.createElement(LiveInstall, { view });
34
+ };
35
+ export async function renderLiveInstall(run) {
36
+ const store = createViewStore({ phase: "scanning", total: 0, verified: 0, flagged: 0 });
37
+ const restoreCursor = () => process.stdout.write(String.fromCharCode(27) + "[?25h");
38
+ process.once("exit", restoreCursor);
39
+ const instance = render(React.createElement(App, { store }));
40
+ try {
41
+ return await run((view) => store.set(view));
42
+ }
43
+ finally {
44
+ store.set({ ...store.get(), phase: "done" });
45
+ await instance.waitUntilExit().catch(() => undefined);
46
+ process.off("exit", restoreCursor);
47
+ }
48
+ }
@@ -0,0 +1,24 @@
1
+ import { createInterface } from "node:readline";
2
+ export function defaultPromptIo() {
3
+ return {
4
+ input: process.stdin,
5
+ output: process.stderr,
6
+ isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY)
7
+ };
8
+ }
9
+ export async function promptYesNo(question, io) {
10
+ if (!io.isTTY) {
11
+ return false;
12
+ }
13
+ const rl = createInterface({ input: io.input, output: io.output });
14
+ try {
15
+ const answer = await new Promise((resolve) => {
16
+ rl.question(`${question} [y/N] `, resolve);
17
+ });
18
+ const normalized = answer.trim().toLowerCase();
19
+ return normalized === "y" || normalized === "yes";
20
+ }
21
+ finally {
22
+ rl.close();
23
+ }
24
+ }
@@ -0,0 +1,116 @@
1
+ import { optionalPackageManagerNames, optionalSupportGate } from "../setup/optional-support.js";
2
+ const supportedManagers = ["npm", "npx", "pnpm", "pnpx", "yarn", "pip", "pipx", "uv", "uvx", "cargo"];
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"]);
7
+ const pipProtected = new Set(["install", "download", "wheel"]);
8
+ const pipxProtected = new Set(["install", "upgrade", "inject", "run"]);
9
+ const uvProtected = new Set(["add", "sync"]);
10
+ const cargoProtected = new Set(["add", "install", "fetch", "update", "build", "test", "check", "run"]);
11
+ const passthrough = new Set(["help", "--help", "-h", "version", "--version", "-v", "list", "ls", "show", "freeze"]);
12
+ export function packageManagerNames() {
13
+ return [...supportedManagers, ...gatedManagers];
14
+ }
15
+ export function isSupportedPackageManager(manager) {
16
+ return supportedManagers.includes(manager);
17
+ }
18
+ export function classifyPackageManagerInvocation(manager, args) {
19
+ if (!isSupportedPackageManager(manager)) {
20
+ return unsupportedClassification(manager, args);
21
+ }
22
+ if (manager === "npx" || manager === "pnpx" || manager === "uvx") {
23
+ return protectedClassification(manager, args, manager, `${manager} fetches and runs package artifacts`);
24
+ }
25
+ if (manager === "npm") {
26
+ return classifyByCommand(manager, args, "npm", jsProtected, "npm install/fetch command", "javascript");
27
+ }
28
+ if (manager === "pnpm") {
29
+ return classifyByCommand(manager, args, "pnpm", pnpmProtected, "pnpm install/fetch command", "javascript");
30
+ }
31
+ if (manager === "yarn") {
32
+ return classifyByCommand(manager, args, "yarn", yarnProtected, "Yarn classic install/fetch command", "javascript");
33
+ }
34
+ if (manager === "pip") {
35
+ return classifyByCommand(manager, args, "pip", pipProtected, "pip install/fetch command", "python");
36
+ }
37
+ if (manager === "pipx") {
38
+ return classifyByCommand(manager, args, "pipx", pipxProtected, "pipx install/fetch command", "python");
39
+ }
40
+ if (manager === "uv") {
41
+ return classifyUv(args);
42
+ }
43
+ return classifyByCommand(manager, args, "cargo", cargoProtected, "Cargo command can fetch crates", "rust");
44
+ }
45
+ function classifyByCommand(manager, args, realBinaryName, protectedCommands, protectedReason, ecosystem) {
46
+ const action = firstCommand(args);
47
+ if (protectedCommands.has(action) || containsFetchSpec(args)) {
48
+ return {
49
+ manager,
50
+ ecosystem,
51
+ kind: "protected",
52
+ action,
53
+ reason: protectedReason,
54
+ realBinaryName,
55
+ args
56
+ };
57
+ }
58
+ return {
59
+ manager,
60
+ ecosystem,
61
+ kind: "passthrough",
62
+ action,
63
+ reason: passthrough.has(action) ? "read-only or local package-manager command" : "not classified as an install/fetch command",
64
+ realBinaryName,
65
+ args
66
+ };
67
+ }
68
+ function classifyUv(args) {
69
+ const action = firstCommand(args);
70
+ if (action === "pip" && pipProtected.has(args[1] ?? "")) {
71
+ return protectedClassification("uv", args, "uv", "uv pip install/fetch command");
72
+ }
73
+ if (action === "tool" && args[1] === "run") {
74
+ return protectedClassification("uv", args, "uv", "uv tool run fetches package artifacts");
75
+ }
76
+ if (uvProtected.has(action) || containsFetchSpec(args)) {
77
+ return protectedClassification("uv", args, "uv", "uv install/fetch command");
78
+ }
79
+ return {
80
+ manager: "uv",
81
+ ecosystem: "python",
82
+ kind: "passthrough",
83
+ action,
84
+ reason: passthrough.has(action) ? "read-only or local uv command" : "not classified as an install/fetch command",
85
+ realBinaryName: "uv",
86
+ args
87
+ };
88
+ }
89
+ function protectedClassification(manager, args, realBinaryName, reason) {
90
+ return {
91
+ manager,
92
+ ecosystem: manager === "cargo" ? "rust" : manager === "pip" || manager === "pipx" || manager === "uv" || manager === "uvx" ? "python" : "javascript",
93
+ kind: "protected",
94
+ action: firstCommand(args),
95
+ reason,
96
+ realBinaryName,
97
+ args
98
+ };
99
+ }
100
+ function unsupportedClassification(manager, args) {
101
+ return {
102
+ manager,
103
+ ecosystem: "gated",
104
+ kind: "unsupported",
105
+ action: firstCommand(args),
106
+ reason: optionalSupportGate(manager).message,
107
+ realBinaryName: manager,
108
+ args
109
+ };
110
+ }
111
+ function firstCommand(args) {
112
+ return args.find((arg) => !arg.startsWith("-")) ?? "";
113
+ }
114
+ function containsFetchSpec(args) {
115
+ return args.some((arg) => /^https?:\/\//.test(arg) || /^git\+https?:\/\//.test(arg) || /\.t(ar\.)?gz(?:$|[#?])/.test(arg));
116
+ }
@@ -0,0 +1,53 @@
1
+ export function buildProxyChildEnv(options) {
2
+ const env = {
3
+ ...options.baseEnv,
4
+ DG_PROXY_ACTIVE: "1"
5
+ };
6
+ const noProxy = mergeNoProxy(options.baseEnv.NO_PROXY ?? options.baseEnv.no_proxy, options.noProxyHosts ?? ["127.0.0.1", "localhost"]);
7
+ if (["npm", "npx", "pnpm", "pnpx", "yarn"].includes(options.manager)) {
8
+ env.HTTP_PROXY = options.proxyUrl;
9
+ env.HTTPS_PROXY = options.proxyUrl;
10
+ env.npm_config_proxy = options.proxyUrl;
11
+ env.npm_config_https_proxy = options.proxyUrl;
12
+ env.NO_PROXY = noProxy;
13
+ env.NODE_EXTRA_CA_CERTS = options.caBundlePath;
14
+ if (options.cacheDir) {
15
+ env.npm_config_cache = options.cacheDir;
16
+ if (options.manager === "yarn") {
17
+ env.YARN_CACHE_FOLDER = options.cacheDir;
18
+ }
19
+ }
20
+ return env;
21
+ }
22
+ if (options.manager === "pip" || options.manager === "pipx") {
23
+ env.http_proxy = options.proxyUrl;
24
+ env.https_proxy = options.proxyUrl;
25
+ env.no_proxy = noProxy;
26
+ env.REQUESTS_CA_BUNDLE = options.caBundlePath;
27
+ env.PIP_CERT = options.caBundlePath;
28
+ env.PIP_NO_CACHE_DIR = "1";
29
+ return env;
30
+ }
31
+ if (options.manager === "uv" || options.manager === "uvx") {
32
+ env.HTTP_PROXY = options.proxyUrl;
33
+ env.HTTPS_PROXY = options.proxyUrl;
34
+ env.ALL_PROXY = options.proxyUrl;
35
+ env.NO_PROXY = noProxy;
36
+ env.SSL_CERT_FILE = options.caBundlePath;
37
+ env.UV_NO_CACHE = "1";
38
+ return env;
39
+ }
40
+ env.HTTPS_PROXY = options.proxyUrl;
41
+ env.https_proxy = options.proxyUrl;
42
+ env.http_proxy = options.proxyUrl;
43
+ env.NO_PROXY = noProxy;
44
+ env.CARGO_HTTP_CAINFO = options.caBundlePath;
45
+ return env;
46
+ }
47
+ function mergeNoProxy(existing, required) {
48
+ const values = new Set((existing ?? "").split(",").map((part) => part.trim()).filter(Boolean));
49
+ for (const host of required) {
50
+ values.add(host);
51
+ }
52
+ return [...values].join(",");
53
+ }