@westbayberry/dg 2.0.11 → 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 +6 -4
- 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 +26 -10
- package/dist/commands/cooldown.js +389 -0
- package/dist/commands/decisions.js +169 -0
- 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 +8 -4
- package/dist/commands/sbom.js +206 -0
- package/dist/commands/scan.js +3 -2
- package/dist/commands/service.js +32 -13
- package/dist/commands/setup.js +197 -27
- package/dist/commands/status.js +5 -2
- 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 +154 -65
- package/dist/decisions/apply.js +128 -0
- package/dist/decisions/remember-prompt.js +92 -0
- package/dist/export-ui/ExportDialog.js +198 -0
- package/dist/install-ui/LiveInstall.js +2 -2
- package/dist/install-ui/block-render.js +21 -4
- package/dist/install-ui/prep-spinner.js +32 -0
- package/dist/install-ui/prompt.js +14 -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 +167 -17
- package/dist/launcher/live-install.js +25 -5
- package/dist/launcher/output-redaction.js +7 -4
- package/dist/launcher/preflight-prompt.js +43 -3
- package/dist/launcher/run.js +100 -86
- package/dist/launcher/spawn-invocation.js +21 -0
- package/dist/policy/cooldown.js +117 -0
- package/dist/policy/evaluate.js +5 -21
- 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/provenance.js +23 -0
- package/dist/presentation/theme.js +7 -7
- package/dist/project/dgfile.js +446 -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 +57 -17
- package/dist/proxy/metadata-map.js +66 -4
- package/dist/proxy/preverified.js +55 -0
- package/dist/proxy/server.js +473 -45
- 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 +51 -17
- package/dist/scan/discovery.js +11 -2
- package/dist/scan/render.js +63 -8
- package/dist/scan/scanner-report.js +42 -9
- package/dist/scan/staged.js +71 -11
- package/dist/scan-ui/LegacyApp.js +12 -16
- package/dist/scan-ui/alt-screen.js +5 -8
- package/dist/scan-ui/components/InteractiveResultsView.js +193 -123
- 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 +46 -4
- package/dist/scan-ui/launch.js +7 -4
- package/dist/scan-ui/shims.js +14 -4
- package/dist/scripts/detect.js +158 -0
- package/dist/scripts/gate.js +170 -0
- 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 +98 -29
- 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/json-file.js +24 -0
- package/dist/util/report-writer.js +57 -0
- package/dist/util/tty-prompt.js +13 -6
- package/dist/verify/local.js +240 -42
- package/dist/verify/package-check.js +86 -18
- package/dist/verify/preflight.js +242 -49
- package/dist/verify/render.js +15 -1
- package/npm-shrinkwrap.json +2383 -0
- package/package.json +14 -8
- package/NOTICE +0 -5
- package/dist/commands/completion.js +0 -116
- package/dist/commands/explain.js +0 -232
- package/dist/commands/unavailable.js +0 -11
- package/dist/telemetry/events.js +0 -40
package/dist/commands/logout.js
CHANGED
|
@@ -3,9 +3,9 @@ import { clearAuthState } from "../auth/store.js";
|
|
|
3
3
|
export const logoutCommand = {
|
|
4
4
|
name: "logout",
|
|
5
5
|
summary: "Remove local Dependency Guardian authentication.",
|
|
6
|
-
usage: "dg logout
|
|
7
|
-
flags: [{ flag: "--yes", summary: "
|
|
8
|
-
details: ["Removes dg-owned auth
|
|
6
|
+
usage: "dg logout",
|
|
7
|
+
flags: [{ flag: "--yes", summary: "Accepted for scripts; logout no longer needs confirmation." }],
|
|
8
|
+
details: ["Removes the dg-owned local auth token only. Config and setup files are not changed; log back in with dg login."],
|
|
9
9
|
handler: (context) => logoutHandler(context.args)
|
|
10
10
|
};
|
|
11
11
|
function logoutHandler(args, env = process.env) {
|
|
@@ -17,13 +17,6 @@ function logoutHandler(args, env = process.env) {
|
|
|
17
17
|
stderr: `dg logout: unknown option '${unknown}'. Run 'dg logout --help'.\n`
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
if (!args.includes("--yes")) {
|
|
21
|
-
return {
|
|
22
|
-
exitCode: EXIT_USAGE,
|
|
23
|
-
stdout: "This removes the dg-owned local auth token only. Config and setup files are not changed.\n",
|
|
24
|
-
stderr: "dg logout requires --yes to confirm.\n"
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
20
|
let removed;
|
|
28
21
|
try {
|
|
29
22
|
removed = clearAuthState();
|
|
@@ -35,7 +28,7 @@ function logoutHandler(args, env = process.env) {
|
|
|
35
28
|
stderr: `dg logout: could not remove the local auth token: ${error instanceof Error ? error.message : "unknown error"}\n`
|
|
36
29
|
};
|
|
37
30
|
}
|
|
38
|
-
const lines = [removed ? "
|
|
31
|
+
const lines = [removed ? "Logged out." : "Already logged out."];
|
|
39
32
|
const activeEnvVars = ["DG_API_KEY", "DG_API_TOKEN"].filter((name) => env[name]);
|
|
40
33
|
if (activeEnvVars.length > 0) {
|
|
41
34
|
lines.push(`${activeEnvVars.join(" and ")} ${activeEnvVars.length === 1 ? "is" : "are"} still set, so env-var auth remains active (file token removed only).`);
|
package/dist/commands/router.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { configCommand } from "./config.js";
|
|
2
|
-
import { completionCommand } from "./completion.js";
|
|
3
2
|
import { auditCommand } from "./audit.js";
|
|
3
|
+
import { cooldownCommand } from "./cooldown.js";
|
|
4
|
+
import { decisionsCommand } from "./decisions.js";
|
|
4
5
|
import { doctorCommand } from "./doctor.js";
|
|
5
|
-
import { explainCommand } from "./explain.js";
|
|
6
6
|
import { guardCommitCommand } from "./guard-commit.js";
|
|
7
|
+
import { agentsCommand } from "./agents.js";
|
|
7
8
|
import { renderCommandHelp, renderRootHelp } from "./help.js";
|
|
8
9
|
import { licensesCommand } from "./licenses.js";
|
|
10
|
+
import { sbomCommand } from "./sbom.js";
|
|
9
11
|
import { loginCommand } from "./login.js";
|
|
10
12
|
import { logoutCommand } from "./logout.js";
|
|
11
13
|
import { packageManagerCommands } from "./wrap.js";
|
|
@@ -21,18 +23,20 @@ import { verifyCommand } from "./verify.js";
|
|
|
21
23
|
import { dgVersion, versionResult } from "./version.js";
|
|
22
24
|
export const commandCatalog = [
|
|
23
25
|
scanCommand,
|
|
26
|
+
sbomCommand,
|
|
24
27
|
verifyCommand,
|
|
25
28
|
setupCommand,
|
|
26
29
|
guardCommitCommand,
|
|
30
|
+
agentsCommand,
|
|
31
|
+
decisionsCommand,
|
|
32
|
+
cooldownCommand,
|
|
27
33
|
uninstallCommand,
|
|
28
34
|
doctorCommand,
|
|
29
35
|
statusCommand,
|
|
30
36
|
...packageManagerCommands(),
|
|
31
37
|
loginCommand,
|
|
32
38
|
logoutCommand,
|
|
33
|
-
explainCommand,
|
|
34
39
|
configCommand,
|
|
35
|
-
completionCommand,
|
|
36
40
|
licensesCommand,
|
|
37
41
|
auditCommand,
|
|
38
42
|
updateCommand,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import { writeReportAtomic } from "../util/report-writer.js";
|
|
4
|
+
import { discoverScanProjects, SBOM_LOCKFILE_ECOSYSTEMS } from "../scan/collect.js";
|
|
5
|
+
import { buildCycloneDxSbom, collectSbomComponents, readRootComponent } from "../sbom/cyclonedx.js";
|
|
6
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
7
|
+
import { createTheme } from "../presentation/theme.js";
|
|
8
|
+
import { buildSbomRows } from "../sbom-ui/inventory.js";
|
|
9
|
+
import { launchSbomTui, shouldLaunchSbomTui } from "../sbom-ui/launch.js";
|
|
10
|
+
import { dgVersion } from "./version.js";
|
|
11
|
+
import { EXIT_TOOL_ERROR, EXIT_USAGE } from "./types.js";
|
|
12
|
+
export const sbomCommand = {
|
|
13
|
+
name: "sbom",
|
|
14
|
+
summary: "Inventory the project's dependencies as a CycloneDX 1.5 software bill of materials.",
|
|
15
|
+
usage: "dg sbom [path] [--output <path>] [--reproducible]",
|
|
16
|
+
args: [{ name: "[path]", summary: "Project directory to inventory (default: current directory)." }],
|
|
17
|
+
flags: [
|
|
18
|
+
{ flag: "--output", value: "<path>", summary: "Write the document to a file without opening the interactive view (alias -o)." },
|
|
19
|
+
{ flag: "--json", summary: "Print the raw CycloneDX document to stdout instead of the interactive view." },
|
|
20
|
+
{ flag: "--reproducible", summary: "Byte-stable output: derive the serial number from the components and drop the timestamp (honors SOURCE_DATE_EPOCH)." }
|
|
21
|
+
],
|
|
22
|
+
examples: ["dg sbom", "dg sbom -o sbom.cdx.json", "dg sbom --json | cyclonedx validate", "dg sbom --reproducible"],
|
|
23
|
+
details: [
|
|
24
|
+
"Reads the project's lockfiles and inventories every resolved dependency as a CycloneDX 1.5 component with its purl, license, and integrity hash.",
|
|
25
|
+
"In a terminal it opens an interactive view: the inventory appears immediately, then dg's scanner runs over the npm and PyPI components and fills in a BLOCK/WARN/PASS verdict per package — malware, provenance downgrades, and cooldown — while cargo stays inventory only. Browse with the arrow keys, search with /, filter with f, and press w to write the document. Signing in unlocks verdicts; without it the view shows the inventory alone.",
|
|
26
|
+
"Piped, with -o, or with --json it stays offline and prints the raw CycloneDX document so you can attach it to a release or feed any CycloneDX-aware tool — no scan, no account needed. Use --reproducible (or set SOURCE_DATE_EPOCH) for identical bytes across runs so a committed SBOM diffs cleanly."
|
|
27
|
+
],
|
|
28
|
+
handler: (context) => runSbomCommand(context)
|
|
29
|
+
};
|
|
30
|
+
export function runSbomCommand(context, cwd = process.cwd(), now = new Date(), env = process.env) {
|
|
31
|
+
const parsed = parseArgs(context.args);
|
|
32
|
+
if ("error" in parsed) {
|
|
33
|
+
return { exitCode: EXIT_USAGE, stdout: "", stderr: `dg sbom: ${parsed.error}. Usage: ${sbomCommand.usage}\n` };
|
|
34
|
+
}
|
|
35
|
+
const root = resolve(cwd, parsed.targetPath ?? ".");
|
|
36
|
+
const projects = discoverScanProjects(root, SBOM_LOCKFILE_ECOSYSTEMS);
|
|
37
|
+
const { components, dropped } = collectSbomComponents(projects);
|
|
38
|
+
const rootComponent = readRootComponent(root);
|
|
39
|
+
const reproducible = parsed.reproducible || env.SOURCE_DATE_EPOCH !== undefined;
|
|
40
|
+
const bom = buildCycloneDxSbom(components, {
|
|
41
|
+
...timestampOption(reproducible, now, env),
|
|
42
|
+
serialNumber: "urn:uuid:00000000-0000-0000-0000-000000000000",
|
|
43
|
+
toolVersion: dgVersion(),
|
|
44
|
+
...(rootComponent.component ? { rootComponent: rootComponent.component } : {})
|
|
45
|
+
});
|
|
46
|
+
const finalBom = {
|
|
47
|
+
...bom,
|
|
48
|
+
serialNumber: reproducible ? deterministicSerialNumber(bom) : `urn:uuid:${randomUUID()}`
|
|
49
|
+
};
|
|
50
|
+
const document = `${JSON.stringify(finalBom, null, 2)}\n`;
|
|
51
|
+
const notes = [];
|
|
52
|
+
if (components.length === 0) {
|
|
53
|
+
notes.push(`dg sbom: no resolvable dependencies found under ${root} — emitting an empty SBOM.`);
|
|
54
|
+
}
|
|
55
|
+
if (dropped.length > 0) {
|
|
56
|
+
notes.push(`dg sbom: omitted ${dropped.length} dependency(ies) without a pinned version (${formatDropped(dropped)}). Pin them (e.g. name==version) to include them.`);
|
|
57
|
+
}
|
|
58
|
+
if (rootComponent.malformed) {
|
|
59
|
+
notes.push(`dg sbom: ${root}/package.json could not be parsed — the SBOM has no named subject component.`);
|
|
60
|
+
}
|
|
61
|
+
const stderr = notes.length > 0 ? `${notes.join("\n")}\n` : "";
|
|
62
|
+
if (parsed.outputPath) {
|
|
63
|
+
try {
|
|
64
|
+
writeReportAtomic(resolve(cwd, parsed.outputPath), document);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return { exitCode: EXIT_TOOL_ERROR, stdout: "", stderr: `dg sbom: could not write ${parsed.outputPath}: ${error instanceof Error ? error.message : "unknown write error"}\n` };
|
|
68
|
+
}
|
|
69
|
+
return { exitCode: 0, stdout: `Wrote CycloneDX SBOM (${components.length} components) to ${parsed.outputPath}\n`, stderr };
|
|
70
|
+
}
|
|
71
|
+
const presentation = resolvePresentation();
|
|
72
|
+
if (parsed.json || !presentation.isTTY) {
|
|
73
|
+
return { exitCode: 0, stdout: document, stderr };
|
|
74
|
+
}
|
|
75
|
+
const subject = finalBom.metadata.component
|
|
76
|
+
? `${finalBom.metadata.component.name}${finalBom.metadata.component.version ? `@${finalBom.metadata.component.version}` : ""}`
|
|
77
|
+
: basename(root) || ".";
|
|
78
|
+
if (shouldLaunchSbomTui({ json: parsed.json, outputPath: parsed.outputPath })) {
|
|
79
|
+
void launchSbomTui({ rows: buildSbomRows(components), dropped, subject, document, env, cwd }).then(() => process.exit(typeof process.exitCode === "number" ? process.exitCode : 0), (error) => {
|
|
80
|
+
process.stderr.write(`dg sbom view failed: ${error instanceof Error ? error.message : "unknown error"}\n`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
84
|
+
}
|
|
85
|
+
return { exitCode: 0, stdout: renderSbomSummary(finalBom, dropped, subject, createTheme(presentation.color)), stderr };
|
|
86
|
+
}
|
|
87
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/gu;
|
|
88
|
+
function visibleWidth(text) {
|
|
89
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
90
|
+
}
|
|
91
|
+
export function renderSbomSummary(bom, dropped, subject, theme, terminalWidth = process.stdout.columns ?? 100) {
|
|
92
|
+
const components = bom.components;
|
|
93
|
+
const total = components.length;
|
|
94
|
+
const count = (prefix) => components.filter((c) => c["bom-ref"].startsWith(prefix)).length;
|
|
95
|
+
const ecosystems = [["npm", count("pkg:npm/")], ["pypi", count("pkg:pypi/")], ["cargo", count("pkg:cargo/")]]
|
|
96
|
+
.filter(([, n]) => n > 0)
|
|
97
|
+
.sort((a, b) => b[1] - a[1]);
|
|
98
|
+
const withLicense = components.filter((c) => c.licenses && c.licenses.length > 0).length;
|
|
99
|
+
const withHash = components.filter((c) => c.hashes && c.hashes.length > 0).length;
|
|
100
|
+
const accent = (text) => theme.paint("accent", text);
|
|
101
|
+
const muted = (text) => theme.paint("muted", text);
|
|
102
|
+
const warn = (text) => theme.paint("warn", text);
|
|
103
|
+
const denomWidth = String(total).length;
|
|
104
|
+
const license = { label: "License", covered: withLicense, deficit: total - withLicense, gap: "unknown" };
|
|
105
|
+
const integrity = { label: "Integrity", covered: withHash, deficit: total - withHash, gap: "no checksum" };
|
|
106
|
+
const gapWidth = Math.max(1, String(license.deficit).length, String(integrity.deficit).length);
|
|
107
|
+
const gapClause = (q) => (q.deficit > 0 ? `${String(q.deficit).padStart(gapWidth)} ${q.gap}` : "");
|
|
108
|
+
const clauseWidth = Math.max(gapClause(license).length, gapClause(integrity).length);
|
|
109
|
+
const qualityRow = (q) => {
|
|
110
|
+
const clause = gapClause(q);
|
|
111
|
+
const painted = clause ? warn(clause) : "";
|
|
112
|
+
const fraction = muted(`${String(q.covered).padStart(denomWidth)} / ${total} covered`);
|
|
113
|
+
return `${muted(q.label.padEnd(10))}${painted}${" ".repeat(clauseWidth - clause.length)} ${fraction}`;
|
|
114
|
+
};
|
|
115
|
+
const breakdown = ecosystems.map(([name, n]) => `${n} ${name}`).join(muted(" · "));
|
|
116
|
+
const rows = [
|
|
117
|
+
`${accent(String(total))} ${total === 1 ? "component" : "components"}${breakdown ? ` ${muted(breakdown)}` : ""}`,
|
|
118
|
+
"",
|
|
119
|
+
qualityRow(license),
|
|
120
|
+
qualityRow(integrity),
|
|
121
|
+
...(dropped.length > 0
|
|
122
|
+
? [`${muted("Omitted".padEnd(10))}${warn(`${String(dropped.length).padStart(gapWidth)} unpinned`)}${muted(", left out of the document")}`]
|
|
123
|
+
: []),
|
|
124
|
+
"",
|
|
125
|
+
muted(`CycloneDX 1.5 · ${subject} · inventory only`)
|
|
126
|
+
];
|
|
127
|
+
const inner = Math.min(Math.max(0, ...rows.map(visibleWidth)), Math.max(24, terminalWidth - 4));
|
|
128
|
+
const bar = muted("─".repeat(inner + 2));
|
|
129
|
+
const side = muted("│");
|
|
130
|
+
const boxed = [
|
|
131
|
+
`${muted("╭")}${bar}${muted("╮")}`,
|
|
132
|
+
...rows.map((row) => `${side} ${row}${" ".repeat(Math.max(0, inner - visibleWidth(row)))} ${side}`),
|
|
133
|
+
`${muted("╰")}${bar}${muted("╯")}`
|
|
134
|
+
];
|
|
135
|
+
const footer = [
|
|
136
|
+
` ${muted("→ write it:")} dg sbom -o sbom.cdx.json ${muted("· --json prints it")}`,
|
|
137
|
+
` ${accent("dg scan")} ${muted("for BLOCK / WARN / PASS verdicts")}`
|
|
138
|
+
];
|
|
139
|
+
return `${[...boxed, ...footer].join("\n")}\n`;
|
|
140
|
+
}
|
|
141
|
+
function timestampOption(reproducible, now, env) {
|
|
142
|
+
if (!reproducible) {
|
|
143
|
+
return { timestamp: now.toISOString() };
|
|
144
|
+
}
|
|
145
|
+
const epoch = env.SOURCE_DATE_EPOCH;
|
|
146
|
+
if (epoch !== undefined && /^\d+$/.test(epoch.trim())) {
|
|
147
|
+
const date = new Date(Number.parseInt(epoch.trim(), 10) * 1000);
|
|
148
|
+
const iso = Number.isFinite(date.getTime()) ? date.toISOString() : "";
|
|
149
|
+
if (/^\d{4}-/u.test(iso)) {
|
|
150
|
+
return { timestamp: iso };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
function deterministicSerialNumber(bom) {
|
|
156
|
+
const hex = createHash("sha256").update(JSON.stringify(bom.components)).digest("hex");
|
|
157
|
+
const version = `4${hex.slice(13, 16)}`;
|
|
158
|
+
const variantNibble = ((Number.parseInt(hex.slice(16, 17) || "0", 16) & 0x3) | 0x8).toString(16);
|
|
159
|
+
const variant = `${variantNibble}${hex.slice(17, 20)}`;
|
|
160
|
+
return `urn:uuid:${hex.slice(0, 8)}-${hex.slice(8, 12)}-${version}-${variant}-${hex.slice(20, 32)}`;
|
|
161
|
+
}
|
|
162
|
+
function formatDropped(dropped) {
|
|
163
|
+
const shown = dropped.slice(0, 5).join(", ");
|
|
164
|
+
return dropped.length > 5 ? `${shown}, +${dropped.length - 5} more` : shown;
|
|
165
|
+
}
|
|
166
|
+
function parseArgs(args) {
|
|
167
|
+
let targetPath = null;
|
|
168
|
+
let outputPath = null;
|
|
169
|
+
let reproducible = false;
|
|
170
|
+
let json = false;
|
|
171
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
172
|
+
const arg = args[i];
|
|
173
|
+
if (arg === undefined) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (arg === "--output" || arg === "-o") {
|
|
177
|
+
const value = args[i + 1];
|
|
178
|
+
if (value === undefined || value.startsWith("-")) {
|
|
179
|
+
return { error: `${arg} needs a path` };
|
|
180
|
+
}
|
|
181
|
+
outputPath = value;
|
|
182
|
+
i += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (arg.startsWith("--output=")) {
|
|
186
|
+
outputPath = arg.slice("--output=".length);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === "--reproducible") {
|
|
190
|
+
reproducible = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (arg === "--json") {
|
|
194
|
+
json = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg.startsWith("-")) {
|
|
198
|
+
return { error: `unknown flag '${arg}'` };
|
|
199
|
+
}
|
|
200
|
+
if (targetPath !== null) {
|
|
201
|
+
return { error: `unexpected argument '${arg}'` };
|
|
202
|
+
}
|
|
203
|
+
targetPath = arg;
|
|
204
|
+
}
|
|
205
|
+
return { targetPath, outputPath, reproducible, json };
|
|
206
|
+
}
|
package/dist/commands/scan.js
CHANGED
|
@@ -8,11 +8,12 @@ export const scanCommand = {
|
|
|
8
8
|
{ flag: "--json", summary: "Machine-readable JSON report." },
|
|
9
9
|
{ flag: "--sarif", summary: "SARIF report for code-scanning tools." },
|
|
10
10
|
{ flag: "--output", value: "<path>", summary: "Write the report to a file instead of stdout (alias -o)." },
|
|
11
|
-
{ flag: "--staged", summary: "Scan only the git-staged lockfile changes (what dg guard-commit runs)." }
|
|
11
|
+
{ flag: "--staged", summary: "Scan only the git-staged lockfile changes (what dg guard-commit runs)." },
|
|
12
|
+
{ flag: "--no-decisions", summary: "Ignore acceptances remembered in dg.json (see dg decisions)." }
|
|
12
13
|
],
|
|
13
14
|
examples: ["dg scan", "dg scan ./packages/api", "dg scan --json -o scan.json", "dg scan --staged"],
|
|
14
15
|
details: [
|
|
15
|
-
"Reads lockfiles, scores each dependency through the scanner, and never changes project files. In a terminal it opens the full-screen results browser; piped or with --json/--sarif it prints machine output. Exit codes: 0 clean, 1 warn, 2 block, 4 analysis incomplete, 64 usage error."
|
|
16
|
+
"Reads lockfiles, scores each dependency through the scanner, and never changes project files. In a terminal it opens the full-screen results browser; piped or with --json/--sarif it prints machine output. Exit codes: 0 clean, 1 warn, 2 block, 4 analysis incomplete, 10 nothing to scan, 64 usage error."
|
|
16
17
|
],
|
|
17
18
|
handler: runScanCommand
|
|
18
19
|
};
|
package/dist/commands/service.js
CHANGED
|
@@ -2,6 +2,8 @@ import { EXIT_UNAVAILABLE, EXIT_USAGE } from "./types.js";
|
|
|
2
2
|
import { renderCommandHelp } from "./help.js";
|
|
3
3
|
import { buildServiceUninstallPlan, buildTrustInstallPlan, buildTrustUninstallPlan, installServiceTrust, readServiceState, renderServicePlan, resolveServicePaths, restartService, ServiceNotConfiguredError, ServiceProxyError, ServiceTrustStoreError, ServiceTrustToolMissingError, startService, stopService, uninstallService, uninstallServiceTrust } from "../service/state.js";
|
|
4
4
|
import { LockBusyError } from "../state/locks.js";
|
|
5
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
6
|
+
import { createTheme } from "../presentation/theme.js";
|
|
5
7
|
const trustCommand = {
|
|
6
8
|
name: "trust",
|
|
7
9
|
summary: "Manage explicit service-mode trust installation.",
|
|
@@ -208,10 +210,12 @@ function mutationPlanWrites(action) {
|
|
|
208
210
|
function serviceMutation(action, run) {
|
|
209
211
|
try {
|
|
210
212
|
const result = run();
|
|
213
|
+
const theme = createTheme(resolvePresentation().color);
|
|
211
214
|
const changed = result.changed ? actionPastTense(action) : `already ${action === "start" ? "running" : "stopped"}`;
|
|
215
|
+
const detail = result.state.running && result.state.proxy ? ` ${theme.paint("muted", `at ${result.state.proxy.proxyUrl}`)}` : "";
|
|
212
216
|
return {
|
|
213
217
|
exitCode: 0,
|
|
214
|
-
stdout:
|
|
218
|
+
stdout: `${theme.paint("pass", "✓")} Service ${changed}${detail} ${theme.paint("muted", "· dg service status")}\n`,
|
|
215
219
|
stderr: ""
|
|
216
220
|
};
|
|
217
221
|
}
|
|
@@ -399,20 +403,34 @@ function parseJsonArgs(command, args) {
|
|
|
399
403
|
};
|
|
400
404
|
}
|
|
401
405
|
function renderServiceStatus(state) {
|
|
406
|
+
const theme = createTheme(resolvePresentation().color);
|
|
407
|
+
const pass = (text) => `${theme.paint("pass", "✓")} ${text}`;
|
|
408
|
+
const warn = (text) => `${theme.paint("warn", "⚠")} ${text}`;
|
|
409
|
+
const configured = state.configured
|
|
410
|
+
? pass("yes")
|
|
411
|
+
: warn(`no — run ${theme.paint("accent", "dg setup --service --yes")}`);
|
|
412
|
+
const running = state.running && state.proxy
|
|
413
|
+
? pass(`yes ${theme.paint("muted", `(${state.proxy.proxyUrl})`)}`)
|
|
414
|
+
: warn(state.configured ? `no — run ${theme.paint("accent", "dg service start")}` : "no");
|
|
415
|
+
const trust = state.trust
|
|
416
|
+
? pass(`${state.trust.provider} ${theme.paint("muted", `(${state.trust.fingerprintSha256.slice(0, 12)}…)`)}`)
|
|
417
|
+
: warn(`not installed — run ${theme.paint("accent", "dg service trust install --yes")}`);
|
|
402
418
|
const lines = [
|
|
403
|
-
"Dependency Guardian service
|
|
419
|
+
"Dependency Guardian service",
|
|
404
420
|
"",
|
|
405
|
-
`
|
|
406
|
-
`
|
|
407
|
-
`
|
|
408
|
-
`trust provider: ${state.trust ? state.trust.provider : "not installed"}`,
|
|
409
|
-
`trust fingerprint: ${state.trust ? state.trust.fingerprintSha256 : "not installed"}`,
|
|
410
|
-
`trust drift: ${state.trustDrift ? state.trustDrift.message : "none"}`,
|
|
411
|
-
`service proxy: ${state.proxy ? state.proxy.proxyUrl : "not running"}`,
|
|
412
|
-
`health endpoint: ${state.proxy ? state.proxy.healthUrl : "not running"}`,
|
|
413
|
-
`last error: ${state.lastError ?? "none"}`,
|
|
414
|
-
`policy synced: ${state.policySyncedAt ?? "never"}`
|
|
421
|
+
` Configured ${configured}`,
|
|
422
|
+
` Running ${running}`,
|
|
423
|
+
` Trust ${trust}`
|
|
415
424
|
];
|
|
425
|
+
if (state.trustDrift) {
|
|
426
|
+
lines.push(` Trust drift ${warn(state.trustDrift.message)}`);
|
|
427
|
+
}
|
|
428
|
+
lines.push(` Policy sync ${state.policySyncedAt ? pass(state.policySyncedAt) : theme.paint("muted", "never")}`);
|
|
429
|
+
if (state.lastError) {
|
|
430
|
+
lines.push(` Last error ${warn(state.lastError)}`);
|
|
431
|
+
}
|
|
432
|
+
lines.push("");
|
|
433
|
+
lines.push(`Full diagnostics: ${theme.paint("accent", "dg service doctor")}`);
|
|
416
434
|
return `${lines.join("\n")}\n`;
|
|
417
435
|
}
|
|
418
436
|
function serviceStatusJson(state) {
|
|
@@ -503,10 +521,11 @@ function actionPastTense(action) {
|
|
|
503
521
|
return "restarted";
|
|
504
522
|
}
|
|
505
523
|
function serviceNotConfiguredResult() {
|
|
524
|
+
const theme = createTheme(resolvePresentation().color);
|
|
506
525
|
return {
|
|
507
526
|
exitCode: EXIT_USAGE,
|
|
508
527
|
stdout: "",
|
|
509
|
-
stderr: "dg service is not configured.
|
|
528
|
+
stderr: `${theme.paint("warn", "dg service is not configured")} — run ${theme.paint("accent", "dg setup --service --yes")} first ${theme.paint("muted", "(no service or trust state was changed)")}\n`
|
|
510
529
|
};
|
|
511
530
|
}
|
|
512
531
|
function trustStoreFailureResult(planText, action, message) {
|