@westbayberry/dg 2.1.0 → 2.2.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 +113 -0
- package/dist/agents/codex.js +65 -0
- package/dist/agents/copilot-cli.js +115 -0
- package/dist/agents/cursor.js +113 -0
- package/dist/agents/gemini.js +107 -0
- package/dist/agents/persistence.js +285 -0
- package/dist/agents/registry.js +127 -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-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 +8 -3
- package/dist/bin/dg.js +59 -52
- package/dist/commands/agents.js +231 -0
- package/dist/commands/audit.js +22 -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 +197 -27
- package/dist/commands/types.js +1 -0
- package/dist/commands/update.js +17 -8
- package/dist/commands/verify.js +8 -5
- package/dist/config/settings.js +14 -44
- package/dist/decisions/remember-prompt.js +5 -10
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +2 -2
- package/dist/install-ui/block-render.js +1 -1
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/launcher/agent-check.js +466 -0
- package/dist/launcher/agent-hook-exec.js +70 -0
- package/dist/launcher/agent-hook-io.js +31 -0
- package/dist/launcher/cargo-cache.js +40 -0
- package/dist/launcher/classify.js +17 -6
- package/dist/launcher/env.js +71 -24
- package/dist/launcher/install-preflight.js +51 -14
- package/dist/launcher/live-install.js +25 -5
- package/dist/launcher/output-redaction.js +7 -4
- package/dist/launcher/preflight-prompt.js +14 -1
- package/dist/launcher/run.js +89 -86
- 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 +145 -6
- 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 +41 -3
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +448 -49
- 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 +12 -11
- 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 +128 -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 +2 -1
- 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 +16 -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/service/state.js +27 -8
- package/dist/service/trust-refresh.js +92 -0
- 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 +73 -39
- 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 +225 -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 +240 -42
- package/dist/verify/package-check.js +84 -28
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +6 -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
package/dist/launcher/run.js
CHANGED
|
@@ -4,41 +4,26 @@ import { constants as osConstants } from "node:os";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { EXIT_UNAVAILABLE } from "../commands/types.js";
|
|
6
6
|
import { loadUserConfig } from "../config/settings.js";
|
|
7
|
+
import { findProjectRoot, loadDgFile } from "../project/dgfile.js";
|
|
8
|
+
import { writeCooldownExemptionsFile } from "../proxy/cooldown-exemptions-file.js";
|
|
9
|
+
import { writePreverifiedFile } from "../proxy/preverified.js";
|
|
7
10
|
import { describeBlockedInstall, describeFlaggedWarn, renderInstallDecision } from "../install-ui/block-render.js";
|
|
8
11
|
import { enforceProtectedInstall } from "../proxy/enforcement.js";
|
|
9
12
|
import { isCiEnv, resolvePresentation } from "../presentation/mode.js";
|
|
13
|
+
import { maybeSetupNudge } from "../runtime/nudges.js";
|
|
10
14
|
import { createTheme } from "../presentation/theme.js";
|
|
11
15
|
import { readProxySessionState } from "../proxy/server.js";
|
|
12
16
|
import { cleanupSessionSync, createSessionSync, resolveDgPaths } from "../state/index.js";
|
|
13
17
|
import { classifyPackageManagerInvocation } from "./classify.js";
|
|
14
18
|
import { buildProxyChildEnv } from "./env.js";
|
|
19
|
+
import { prepareCargoHome, userCargoHome } from "./cargo-cache.js";
|
|
15
20
|
import { cachedPipResolution } from "./install-preflight.js";
|
|
16
|
-
import { parsePipReportInstallCount } from "./pip-report.js";
|
|
17
21
|
import { createStreamRedactor, redactSecrets } from "./output-redaction.js";
|
|
22
|
+
import { resolveSpawnInvocation } from "./spawn-invocation.js";
|
|
18
23
|
import { resolveRealBinary } from "./resolve-real-binary.js";
|
|
19
24
|
import { runScriptGateAfterInstall } from "../scripts/gate.js";
|
|
20
25
|
export const EXIT_INSTALL_BLOCKED = 2;
|
|
21
|
-
|
|
22
|
-
const CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
23
|
-
export function resolveSpawnInvocation(binary, args, platform = process.platform) {
|
|
24
|
-
if (platform !== "win32" || !CMD_SCRIPT_PATTERN.test(binary)) {
|
|
25
|
-
return { command: binary, args, windowsVerbatimArguments: false };
|
|
26
|
-
}
|
|
27
|
-
const commandLine = [escapeCmdCommand(binary), ...args.map(escapeCmdArgument)].join(" ");
|
|
28
|
-
return {
|
|
29
|
-
command: process.env.comspec ?? "cmd.exe",
|
|
30
|
-
args: ["/d", "/s", "/c", `"${commandLine}"`],
|
|
31
|
-
windowsVerbatimArguments: true
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function escapeCmdCommand(command) {
|
|
35
|
-
return command.replace(CMD_META_CHARS, "^$1");
|
|
36
|
-
}
|
|
37
|
-
// cmd shims parse their command line twice, hence the doubled meta-char escape (cross-spawn's algorithm)
|
|
38
|
-
function escapeCmdArgument(argument) {
|
|
39
|
-
const quoted = `"${argument.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
40
|
-
return quoted.replace(CMD_META_CHARS, "^$1").replace(CMD_META_CHARS, "^$1");
|
|
41
|
-
}
|
|
26
|
+
export { resolveSpawnInvocation } from "./spawn-invocation.js";
|
|
42
27
|
export function shimDepth(env) {
|
|
43
28
|
const parsed = Number.parseInt(env.DG_SHIM_DEPTH ?? "", 10);
|
|
44
29
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
|
|
@@ -130,7 +115,7 @@ export async function runPackageManager(manager, args, options = {}) {
|
|
|
130
115
|
return {
|
|
131
116
|
exitCode: child.exitCode,
|
|
132
117
|
stdout: streamedOut(child.stdout, options),
|
|
133
|
-
stderr: `${rendered}${streamedErr(child.stderr, options)}${scriptGateLine(plan, child.exitCode, options)}`
|
|
118
|
+
stderr: `${rendered}${streamedErr(child.stderr, options)}${scriptGateLine(plan, child.exitCode, options)}${setupNudgeAfterInstall(plan, child.exitCode, options)}`
|
|
134
119
|
};
|
|
135
120
|
}
|
|
136
121
|
const child = await spawnPackageManager(plan, args, options);
|
|
@@ -152,6 +137,7 @@ async function runWithProductionProxy(plan, args, options) {
|
|
|
152
137
|
}
|
|
153
138
|
const restoreSignalHandlers = installProxySignalHandlers(proxy);
|
|
154
139
|
try {
|
|
140
|
+
const cacheDir = prepareProxyCacheDir(plan.classification.manager, proxy.session.dir, env);
|
|
155
141
|
const proxiedPlan = {
|
|
156
142
|
...plan,
|
|
157
143
|
childEnv: buildProxyChildEnv({
|
|
@@ -159,7 +145,7 @@ async function runWithProductionProxy(plan, args, options) {
|
|
|
159
145
|
baseEnv: plan.childEnv,
|
|
160
146
|
proxyUrl: proxy.proxyUrl,
|
|
161
147
|
caBundlePath: proxy.session.files.ca,
|
|
162
|
-
cacheDir
|
|
148
|
+
cacheDir
|
|
163
149
|
})
|
|
164
150
|
};
|
|
165
151
|
const child = await spawnPackageManager(proxiedPlan, args, options);
|
|
@@ -196,7 +182,7 @@ async function runWithProductionProxy(plan, args, options) {
|
|
|
196
182
|
return {
|
|
197
183
|
exitCode: child.exitCode,
|
|
198
184
|
stdout: streamedOut(child.stdout, options),
|
|
199
|
-
stderr: `${rendered}${streamedErr(child.stderr, options)}${scriptGateLine(plan, child.exitCode, options)}`
|
|
185
|
+
stderr: `${rendered}${streamedErr(child.stderr, options)}${scriptGateLine(plan, child.exitCode, options)}${setupNudgeAfterInstall(plan, child.exitCode, options)}`
|
|
200
186
|
};
|
|
201
187
|
}
|
|
202
188
|
finally {
|
|
@@ -214,6 +200,12 @@ function scriptGateLine(plan, exitCode, options) {
|
|
|
214
200
|
...(options.projectDir ? { projectDir: options.projectDir } : {})
|
|
215
201
|
});
|
|
216
202
|
}
|
|
203
|
+
function setupNudgeAfterInstall(plan, exitCode, options) {
|
|
204
|
+
if (exitCode !== 0) {
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
return maybeSetupNudge(plan.classification.manager, { env: options.env ?? process.env });
|
|
208
|
+
}
|
|
217
209
|
export function deriveLiveView(state, phase, resolvedTotal) {
|
|
218
210
|
const verified = state.decisions.filter((decision) => decision.action === "pass").length;
|
|
219
211
|
const warnDecisions = state.decisions.filter((decision) => decision.action === "warn");
|
|
@@ -232,57 +224,43 @@ export function deriveLiveView(state, phase, resolvedTotal) {
|
|
|
232
224
|
...(blocked ? { blocked: describeBlockedInstall(blocked) } : {})
|
|
233
225
|
};
|
|
234
226
|
}
|
|
235
|
-
function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const finish = (value) => {
|
|
243
|
-
if (settled)
|
|
227
|
+
export function prepareProxyWorker(classification, env) {
|
|
228
|
+
const worker = startProxyWorker(classification, env, undefined);
|
|
229
|
+
let discarded = false;
|
|
230
|
+
return {
|
|
231
|
+
worker,
|
|
232
|
+
discard: () => {
|
|
233
|
+
if (discarded) {
|
|
244
234
|
return;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
const invocation = resolveSpawnInvocation(binary, [...args, "--dry-run", "--report", "-", "--quiet"]);
|
|
253
|
-
child = spawn(invocation.command, [...invocation.args], {
|
|
254
|
-
env,
|
|
255
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
256
|
-
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
235
|
+
}
|
|
236
|
+
discarded = true;
|
|
237
|
+
void worker.then((proxy) => {
|
|
238
|
+
if (!("decision" in proxy)) {
|
|
239
|
+
stopProxyWorker(proxy);
|
|
240
|
+
}
|
|
257
241
|
});
|
|
258
242
|
}
|
|
259
|
-
|
|
260
|
-
finish(undefined);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
register(child);
|
|
264
|
-
timer = setTimeout(() => {
|
|
265
|
-
try {
|
|
266
|
-
child.kill();
|
|
267
|
-
}
|
|
268
|
-
catch { /* already exited */ }
|
|
269
|
-
finish(undefined);
|
|
270
|
-
}, 30_000);
|
|
271
|
-
child.stdout?.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
|
|
272
|
-
child.on("error", () => finish(undefined));
|
|
273
|
-
child.on("close", (code) => finish(code === 0 ? parsePipReportInstallCount(stdout) : undefined));
|
|
274
|
-
});
|
|
243
|
+
};
|
|
275
244
|
}
|
|
276
|
-
export async function runWithProductionProxyLive(plan, args, options, onView) {
|
|
245
|
+
export async function runWithProductionProxyLive(plan, args, options, onView, prepared) {
|
|
277
246
|
if (options.onStdout || options.onStderr) {
|
|
278
247
|
throw new Error("live install mode renders its own UI and owns the terminal; streaming output callbacks are not supported");
|
|
279
248
|
}
|
|
280
249
|
const env = options.env ?? process.env;
|
|
281
250
|
maybeWarnRootWithoutState(env);
|
|
282
|
-
|
|
251
|
+
if (prepared && options.forceOverride) {
|
|
252
|
+
prepared.discard();
|
|
253
|
+
prepared = undefined;
|
|
254
|
+
}
|
|
255
|
+
const proxy = prepared
|
|
256
|
+
? await prepared.worker
|
|
257
|
+
: await startProxyWorker(plan.classification, env, options.forceOverride);
|
|
283
258
|
if ("decision" in proxy) {
|
|
284
259
|
return { exitCode: EXIT_INSTALL_BLOCKED, stdout: "", stderr: redactSecrets(renderInstallDecision(proxy.decision)) };
|
|
285
260
|
}
|
|
261
|
+
if (options.preverified && options.preverified.length > 0) {
|
|
262
|
+
writePreverifiedFile(proxy.session.dir, options.preverified);
|
|
263
|
+
}
|
|
286
264
|
const restoreSignalHandlers = installProxySignalHandlers(proxy);
|
|
287
265
|
try {
|
|
288
266
|
const childEnv = buildProxyChildEnv({
|
|
@@ -290,22 +268,10 @@ export async function runWithProductionProxyLive(plan, args, options, onView) {
|
|
|
290
268
|
baseEnv: plan.childEnv,
|
|
291
269
|
proxyUrl: proxy.proxyUrl,
|
|
292
270
|
caBundlePath: proxy.session.files.ca,
|
|
293
|
-
cacheDir:
|
|
271
|
+
cacheDir: prepareProxyCacheDir(plan.classification.manager, proxy.session.dir, env)
|
|
294
272
|
});
|
|
295
273
|
const spawner = options.spawner ?? defaultSpawner;
|
|
296
|
-
|
|
297
|
-
let dryRunChild;
|
|
298
|
-
if (plan.classification.manager === "pip") {
|
|
299
|
-
const cached = cachedPipResolution(plan.realBinary.path ?? "", args);
|
|
300
|
-
if (cached) {
|
|
301
|
-
resolvedTotal = cached.count;
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
void resolvePipInstallTotal(plan.realBinary.path ?? "", args, childEnv, (child) => { dryRunChild = child; })
|
|
305
|
-
.then((count) => { resolvedTotal = count; })
|
|
306
|
-
.catch(() => undefined);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
274
|
+
const resolvedTotal = plan.classification.manager === "pip" ? cachedPipResolution(plan.realBinary.path ?? "", args)?.count : undefined;
|
|
309
275
|
const poll = setInterval(() => {
|
|
310
276
|
onView(deriveLiveView(readProxySessionState(proxy.session), "scanning", resolvedTotal));
|
|
311
277
|
}, 90);
|
|
@@ -319,12 +285,6 @@ export async function runWithProductionProxyLive(plan, args, options, onView) {
|
|
|
319
285
|
}
|
|
320
286
|
finally {
|
|
321
287
|
clearInterval(poll);
|
|
322
|
-
if (dryRunChild) {
|
|
323
|
-
try {
|
|
324
|
-
dryRunChild.kill();
|
|
325
|
-
}
|
|
326
|
-
catch { /* already exited */ }
|
|
327
|
-
}
|
|
328
288
|
}
|
|
329
289
|
const proxyState = readProxySessionState(proxy.session);
|
|
330
290
|
onView(deriveLiveView(proxyState, "done", resolvedTotal));
|
|
@@ -342,13 +302,20 @@ export async function runWithProductionProxyLive(plan, args, options, onView) {
|
|
|
342
302
|
const outcome = installOutcome(finished.stdout);
|
|
343
303
|
return { exitCode: EXIT_INSTALL_BLOCKED, stdout: outcome, stderr: cacheOnlyNotice(outcome.length > 0) };
|
|
344
304
|
}
|
|
345
|
-
return { exitCode: finished.exitCode, stdout: installOutcome(finished.stdout), stderr: scriptGateLine(plan, finished.exitCode, options) };
|
|
305
|
+
return { exitCode: finished.exitCode, stdout: installOutcome(finished.stdout), stderr: `${scriptGateLine(plan, finished.exitCode, options)}${setupNudgeAfterInstall(plan, finished.exitCode, options)}` };
|
|
346
306
|
}
|
|
347
307
|
finally {
|
|
348
308
|
restoreSignalHandlers();
|
|
349
309
|
stopProxyWorker(proxy);
|
|
350
310
|
}
|
|
351
311
|
}
|
|
312
|
+
function prepareProxyCacheDir(manager, sessionDir, env) {
|
|
313
|
+
const cacheDir = `${sessionDir}/pm-cache`;
|
|
314
|
+
if (manager === "cargo") {
|
|
315
|
+
prepareCargoHome(cacheDir, userCargoHome(env));
|
|
316
|
+
}
|
|
317
|
+
return cacheDir;
|
|
318
|
+
}
|
|
352
319
|
function cacheOnlyNotice(installedFresh) {
|
|
353
320
|
const theme = createTheme(resolvePresentation().color);
|
|
354
321
|
const reason = installedFresh ? "used a saved copy" : "already installed";
|
|
@@ -436,6 +403,38 @@ function renderDecisions(decisions, env) {
|
|
|
436
403
|
const visible = isCiEnv(env) ? decisions.filter((decision) => decision.action !== "pass") : decisions;
|
|
437
404
|
return visible.map((decision) => redactSecrets(renderInstallDecision(decision))).join("");
|
|
438
405
|
}
|
|
406
|
+
export function loadProjectCooldownExemptions(env, cwd = process.cwd()) {
|
|
407
|
+
try {
|
|
408
|
+
const root = findProjectRoot(cwd, env);
|
|
409
|
+
if (!root) {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
const file = loadDgFile(root);
|
|
413
|
+
return file.readable ? file.cooldownExemptions : [];
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
export function dgFileExemptionsNotice(exemptions) {
|
|
420
|
+
if (exemptions.length === 0) {
|
|
421
|
+
return "";
|
|
422
|
+
}
|
|
423
|
+
const plural = exemptions.length === 1 ? "exemption" : "exemptions";
|
|
424
|
+
return `dg: applying ${exemptions.length} cooldown ${plural} from this project's dg.json\n`;
|
|
425
|
+
}
|
|
426
|
+
let dgFileExemptionNoticeWritten = false;
|
|
427
|
+
function maybeNoticeDgFileExemptions(exemptions) {
|
|
428
|
+
if (dgFileExemptionNoticeWritten) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const notice = dgFileExemptionsNotice(exemptions);
|
|
432
|
+
if (!notice) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
dgFileExemptionNoticeWritten = true;
|
|
436
|
+
process.stderr.write(notice);
|
|
437
|
+
}
|
|
439
438
|
async function startProxyWorker(classification, env, forceOverride) {
|
|
440
439
|
const paths = resolveDgPaths(env);
|
|
441
440
|
const session = createSessionSync(paths);
|
|
@@ -450,11 +449,15 @@ async function startProxyWorker(classification, env, forceOverride) {
|
|
|
450
449
|
throw new Error("production proxy worker is not built");
|
|
451
450
|
}
|
|
452
451
|
const config = loadUserConfig(env);
|
|
452
|
+
const exemptions = loadProjectCooldownExemptions(env);
|
|
453
|
+
maybeNoticeDgFileExemptions(exemptions);
|
|
454
|
+
const exemptionsEnv = writeCooldownExemptionsFile(session.dir, exemptions);
|
|
453
455
|
const child = spawn(process.execPath, [workerPath, sessionBootstrapPath, config.api.baseUrl], {
|
|
454
456
|
env: {
|
|
455
457
|
...env,
|
|
456
458
|
DG_PROXY_CLASSIFICATION: JSON.stringify(classification),
|
|
457
|
-
...(forceOverride ? { DG_FORCE_OVERRIDE_REQUEST: JSON.stringify(forceOverride) } : {})
|
|
459
|
+
...(forceOverride ? { DG_FORCE_OVERRIDE_REQUEST: JSON.stringify(forceOverride) } : {}),
|
|
460
|
+
...exemptionsEnv
|
|
458
461
|
},
|
|
459
462
|
stdio: ["pipe", "pipe", "pipe"]
|
|
460
463
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const CMD_SCRIPT_PATTERN = /\.(cmd|bat)$/i;
|
|
2
|
+
const CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
3
|
+
export function resolveSpawnInvocation(binary, args, platform = process.platform) {
|
|
4
|
+
if (platform !== "win32" || !CMD_SCRIPT_PATTERN.test(binary)) {
|
|
5
|
+
return { command: binary, args, windowsVerbatimArguments: false };
|
|
6
|
+
}
|
|
7
|
+
const commandLine = [escapeCmdCommand(binary), ...args.map(escapeCmdArgument)].join(" ");
|
|
8
|
+
return {
|
|
9
|
+
command: process.env.comspec ?? "cmd.exe",
|
|
10
|
+
args: ["/d", "/s", "/c", `"${commandLine}"`],
|
|
11
|
+
windowsVerbatimArguments: true
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function escapeCmdCommand(command) {
|
|
15
|
+
return command.replace(CMD_META_CHARS, "^$1");
|
|
16
|
+
}
|
|
17
|
+
// cmd shims parse their command line twice, hence the doubled meta-char escape (cross-spawn's algorithm)
|
|
18
|
+
function escapeCmdArgument(argument) {
|
|
19
|
+
const quoted = `"${argument.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
20
|
+
return quoted.replace(CMD_META_CHARS, "^$1").replace(CMD_META_CHARS, "^$1");
|
|
21
|
+
}
|
package/dist/policy/cooldown.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { parseCooldownAge } from "../config/settings.js";
|
|
2
|
+
import { cooldownExemptionActive } from "../project/dgfile.js";
|
|
3
|
+
import { canonicalCooldownName, normalizePypiName } from "./pypi-name.js";
|
|
4
|
+
export { normalizePypiName };
|
|
2
5
|
export function durationToDays(value) {
|
|
3
6
|
const trimmed = value.trim();
|
|
4
7
|
if (trimmed === "" || trimmed === "0" || trimmed === "off") {
|
|
@@ -28,23 +31,33 @@ export function effectiveCooldownAge(config, env, ecosystem) {
|
|
|
28
31
|
: config.cooldown.cargoAge;
|
|
29
32
|
return perEcosystem !== "" ? perEcosystem : config.cooldown.age;
|
|
30
33
|
}
|
|
31
|
-
function normalizePypiName(name) {
|
|
32
|
-
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
33
|
-
}
|
|
34
34
|
export function isCooldownExempt(packageName, exempt, ecosystem) {
|
|
35
|
-
const name = ecosystem
|
|
35
|
+
const name = ecosystem ? canonicalCooldownName(ecosystem, packageName) : packageName;
|
|
36
36
|
return exempt
|
|
37
37
|
.split(",")
|
|
38
38
|
.map((entry) => entry.trim())
|
|
39
39
|
.filter(Boolean)
|
|
40
40
|
.some((pattern) => {
|
|
41
|
-
const candidate = ecosystem
|
|
41
|
+
const candidate = ecosystem ? canonicalCooldownName(ecosystem, pattern) : pattern;
|
|
42
42
|
if (candidate.endsWith("*")) {
|
|
43
43
|
return name.startsWith(candidate.slice(0, -1));
|
|
44
44
|
}
|
|
45
45
|
return name === candidate;
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
+
export function isCooldownExemptByDgFile(packageName, ecosystem, exemptions, now = new Date()) {
|
|
49
|
+
if (ecosystem !== "npm" && ecosystem !== "pypi" && ecosystem !== "cargo") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const target = canonicalCooldownName(ecosystem, packageName);
|
|
53
|
+
return exemptions.some((e) => {
|
|
54
|
+
if (e.ecosystem !== ecosystem) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const candidate = canonicalCooldownName(ecosystem, e.name);
|
|
58
|
+
return candidate === target && cooldownExemptionActive(e, now);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
48
61
|
export function cooldownRequestParam(config, env, ecosystem, packageName) {
|
|
49
62
|
const minAgeDays = durationToDays(effectiveCooldownAge(config, env, ecosystem));
|
|
50
63
|
if (minAgeDays <= 0) {
|
package/dist/policy/evaluate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { recordAuditEvent } from "../audit/events.js";
|
|
2
2
|
import { DEFAULT_CONFIG } from "../config/settings.js";
|
|
3
3
|
export function resolveEffectivePolicy(options) {
|
|
4
4
|
if (options.orgPolicy) {
|
|
@@ -69,21 +69,21 @@ export function applyForceOverride(options, env = process.env) {
|
|
|
69
69
|
return {
|
|
70
70
|
allowed: false,
|
|
71
71
|
reason: "force override was not requested",
|
|
72
|
-
|
|
72
|
+
auditRecorded: false
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
if (options.currentAction !== "block") {
|
|
76
76
|
return {
|
|
77
77
|
allowed: false,
|
|
78
78
|
reason: "force override is only valid for block decisions",
|
|
79
|
-
|
|
79
|
+
auditRecorded: false
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
if (!options.policy.allowForceOverride) {
|
|
83
83
|
return {
|
|
84
84
|
allowed: false,
|
|
85
85
|
reason: "force override is disabled by policy",
|
|
86
|
-
|
|
86
|
+
auditRecorded: false
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
const event = {
|
|
@@ -93,11 +93,10 @@ export function applyForceOverride(options, env = process.env) {
|
|
|
93
93
|
policyMode: options.policy.mode,
|
|
94
94
|
createdAt: (options.now ?? new Date()).toISOString()
|
|
95
95
|
};
|
|
96
|
-
recordAuditEvent(event, env);
|
|
97
96
|
return {
|
|
98
97
|
allowed: true,
|
|
99
98
|
reason: event.reason,
|
|
100
|
-
|
|
99
|
+
auditRecorded: recordAuditEvent(event, env)
|
|
101
100
|
};
|
|
102
101
|
}
|
|
103
102
|
function findTrustedAllowlist(packageName, policy, allowlists) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function normalizePypiName(name) {
|
|
2
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
3
|
+
}
|
|
4
|
+
// crates.io is case-insensitive and treats '-' and '_' as equivalent, so fold
|
|
5
|
+
// both to a single canonical form for exemption matching.
|
|
6
|
+
export function normalizeCargoName(name) {
|
|
7
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
8
|
+
}
|
|
9
|
+
export function canonicalCooldownName(ecosystem, name) {
|
|
10
|
+
if (ecosystem === "pypi") {
|
|
11
|
+
return normalizePypiName(name);
|
|
12
|
+
}
|
|
13
|
+
if (ecosystem === "cargo") {
|
|
14
|
+
return normalizeCargoName(name);
|
|
15
|
+
}
|
|
16
|
+
return name;
|
|
17
|
+
}
|
|
@@ -19,8 +19,9 @@ export function colorEnabled(input) {
|
|
|
19
19
|
if (input.forceColorFlag) {
|
|
20
20
|
return true;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const forceColor = input.env.FORCE_COLOR;
|
|
23
|
+
if (forceColor !== undefined) {
|
|
24
|
+
return forceColor !== "0" && forceColor !== "false";
|
|
24
25
|
}
|
|
25
26
|
if (input.noColorFlag) {
|
|
26
27
|
return false;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const SITE = "https://westbayberry.com";
|
|
2
|
+
// Only npm and pypi have public, indexable per-package pages today; cargo and
|
|
3
|
+
// other ecosystems have none, so return null rather than link to a 404.
|
|
4
|
+
export function packagePageUrl(ecosystem, name) {
|
|
5
|
+
if (ecosystem !== "npm" && ecosystem !== "pypi") {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return `${SITE}/${ecosystem}/${name}`;
|
|
9
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const ANSI = {
|
|
2
|
-
block: "
|
|
3
|
-
warn: "
|
|
4
|
-
pass: "
|
|
5
|
-
unknown: "
|
|
6
|
-
muted: "
|
|
7
|
-
accent: "
|
|
2
|
+
block: "\u001b[31m",
|
|
3
|
+
warn: "\u001b[33m",
|
|
4
|
+
pass: "\u001b[32m",
|
|
5
|
+
unknown: "\u001b[36m",
|
|
6
|
+
muted: "\u001b[2m",
|
|
7
|
+
accent: "\u001b[1m"
|
|
8
8
|
};
|
|
9
|
-
const RESET = "
|
|
9
|
+
const RESET = "\u001b[0m";
|
|
10
10
|
const BADGES = {
|
|
11
11
|
block: { word: "BLOCK", glyph: "✘", role: "block" },
|
|
12
12
|
warn: { word: "WARN", glyph: "⚠", role: "warn" },
|