@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
package/dist/scan/discovery.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { resolveToolPath } from "../util/external-tool.js";
|
|
4
5
|
const IGNORED_DIRECTORIES = new Set([
|
|
5
6
|
".git",
|
|
6
7
|
".hg",
|
|
@@ -113,8 +114,12 @@ export function gitIgnoredDirectories(root) {
|
|
|
113
114
|
return ignored;
|
|
114
115
|
}
|
|
115
116
|
function insideGitWorkTree(root) {
|
|
117
|
+
const git = resolveToolPath("git");
|
|
118
|
+
if (!git) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
116
121
|
try {
|
|
117
|
-
const out = execFileSync(
|
|
122
|
+
const out = execFileSync(git, ["-C", root, "rev-parse", "--is-inside-work-tree"], {
|
|
118
123
|
encoding: "utf8",
|
|
119
124
|
timeout: 3000,
|
|
120
125
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -126,8 +131,12 @@ function insideGitWorkTree(root) {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
function checkIgnoreBatch(root, directories) {
|
|
134
|
+
const git = resolveToolPath("git");
|
|
135
|
+
if (!git) {
|
|
136
|
+
return new Set();
|
|
137
|
+
}
|
|
129
138
|
try {
|
|
130
|
-
const out = execFileSync(
|
|
139
|
+
const out = execFileSync(git, ["-C", root, "check-ignore", "--stdin", "-z"], {
|
|
131
140
|
input: directories.join("\0"),
|
|
132
141
|
encoding: "utf8",
|
|
133
142
|
timeout: 5000,
|
package/dist/scan/render.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createTheme } from "../presentation/theme.js";
|
|
2
|
+
import { packagePageUrl } from "../presentation/package-page.js";
|
|
3
|
+
import { sanitizeDeep } from "../security/sanitize.js";
|
|
2
4
|
const DETAIL_PROJECT_LIMIT = 8;
|
|
3
5
|
const CLEAN_PROJECT_PREVIEW_LIMIT = 5;
|
|
4
6
|
const FINDING_GROUP_PREVIEW_LIMIT = 3;
|
|
@@ -16,7 +18,8 @@ const SCANNER_SKIP_MESSAGES = {
|
|
|
16
18
|
no_lockfile: "no lockfile found — server verification skipped (local heuristics only)",
|
|
17
19
|
empty_lockfile: "lockfile contains no scannable packages — server verification skipped (local heuristics only)"
|
|
18
20
|
};
|
|
19
|
-
export function renderTextReport(
|
|
21
|
+
export function renderTextReport(rawReport, terminalWidth = terminalWidthFromEnv(), theme = createTheme(false), scannerNotice) {
|
|
22
|
+
const report = sanitizeDeep(rawReport);
|
|
20
23
|
const width = Math.max(48, Math.min(terminalWidth, 140));
|
|
21
24
|
const cleanProjects = report.projects.filter((project) => project.findings.length === 0);
|
|
22
25
|
const findingProjects = report.projects.filter((project) => project.findings.length > 0);
|
|
@@ -95,11 +98,31 @@ export function renderTextReport(report, terminalWidth = terminalWidthFromEnv(),
|
|
|
95
98
|
lines.push("");
|
|
96
99
|
}
|
|
97
100
|
}
|
|
101
|
+
lines.push(...packagePageLines(report, theme));
|
|
98
102
|
for (const error of report.errors) {
|
|
99
103
|
lines.push(...wrapLine(`ERROR ${error.location}: `, error.message, width));
|
|
100
104
|
}
|
|
101
105
|
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
102
106
|
}
|
|
107
|
+
function packagePageLines(report, theme) {
|
|
108
|
+
const urls = [];
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
for (const pkg of report.scanner?.packages ?? []) {
|
|
111
|
+
const action = pkg.action ?? "pass";
|
|
112
|
+
if (action !== "block" && action !== "warn") {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const url = packagePageUrl(pkg.ecosystem ?? "", pkg.name);
|
|
116
|
+
if (url && !seen.has(url)) {
|
|
117
|
+
seen.add(url);
|
|
118
|
+
urls.push(url);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (urls.length === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
return ["Full report for each flagged package:", ...urls.map((url) => ` ${theme.paint("muted", url)}`), ""];
|
|
125
|
+
}
|
|
103
126
|
function provenanceDowngradeLines(packages, theme) {
|
|
104
127
|
return packages
|
|
105
128
|
.filter((pkg) => pkg.provenance?.downgrade)
|
|
@@ -132,8 +155,8 @@ function formatProject(project, width) {
|
|
|
132
155
|
}
|
|
133
156
|
return lines;
|
|
134
157
|
}
|
|
135
|
-
export function renderJsonReport(report, scannerUnavailable = false) {
|
|
136
|
-
return `${JSON.stringify({ ...report, status: displayScanStatus(report), scannerUnavailable }, null, 2)}\n`;
|
|
158
|
+
export function renderJsonReport(report, scannerUnavailable = false, nothingToScan = false) {
|
|
159
|
+
return `${JSON.stringify({ schemaVersion: 1, ...report, status: nothingToScan ? "nothing_to_scan" : displayScanStatus(report), scannerUnavailable }, null, 2)}\n`;
|
|
137
160
|
}
|
|
138
161
|
export function renderSarifReport(report) {
|
|
139
162
|
const rules = uniqueFindings(report.findings).map((finding) => ({
|
|
@@ -281,7 +304,8 @@ function wrapLine(prefix, text, width) {
|
|
|
281
304
|
return lines.map((line, index) => `${index === 0 ? prefix : continuation}${line}`);
|
|
282
305
|
}
|
|
283
306
|
function terminalWidthFromEnv() {
|
|
284
|
-
const
|
|
307
|
+
const override = process.env.NODE_ENV === "test" ? process.env.DG_TEST_TERMINAL_WIDTH : undefined;
|
|
308
|
+
const value = override ?? String(process.stdout.columns ?? 100);
|
|
285
309
|
const parsed = Number.parseInt(value, 10);
|
|
286
310
|
return Number.isFinite(parsed) ? parsed : 100;
|
|
287
311
|
}
|
|
@@ -59,16 +59,16 @@ export function runScannerScan(targetPath, localReport, env = process.env, decis
|
|
|
59
59
|
error: { kind: "invalid_response", message: "scanner worker returned unreadable output" }
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
const report = buildScannerReport(localReport, response, total);
|
|
63
|
-
if (!decisionsFile?.readable) {
|
|
64
|
-
return { kind: "report", report };
|
|
65
|
-
}
|
|
66
62
|
const ecosystems = new Map();
|
|
67
63
|
for (const group of groups) {
|
|
68
64
|
for (const pkg of group.packages) {
|
|
69
65
|
ecosystems.set(packageKey(pkg.name, pkg.version), group.ecosystem);
|
|
70
66
|
}
|
|
71
67
|
}
|
|
68
|
+
const report = buildScannerReport(localReport, response, total, ecosystems);
|
|
69
|
+
if (!decisionsFile?.readable) {
|
|
70
|
+
return { kind: "report", report };
|
|
71
|
+
}
|
|
72
72
|
return { kind: "report", report: annotateReportWithDecisions(report, decisionsFile, ecosystems) };
|
|
73
73
|
}
|
|
74
74
|
export function tryScannerScan(targetPath, localReport, env = process.env, decisionsFile = null) {
|
|
@@ -130,9 +130,9 @@ function parseWorkerError(stdout) {
|
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
132
132
|
function lockfileParseError(projects, collected) {
|
|
133
|
-
const parseErrors = collected.parseErrors
|
|
133
|
+
const parseErrors = collected.parseErrors;
|
|
134
134
|
const detail = parseErrors
|
|
135
|
-
.map((entry) => [entry.
|
|
135
|
+
.map((entry) => [entry.file, entry.reason].filter(Boolean).join(": "))
|
|
136
136
|
.filter((line) => line.length > 0)
|
|
137
137
|
.join("; ");
|
|
138
138
|
const lockfiles = [...new Set(projects.map((project) => project.depFile))].join(", ");
|
|
@@ -143,8 +143,14 @@ function lockfileParseError(projects, collected) {
|
|
|
143
143
|
: `found ${projects.length} lockfile${projects.length === 1 ? "" : "s"} (${lockfiles}) but no packages could be parsed`
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
export function buildScannerReport(localReport, response, analyzedCount) {
|
|
147
|
-
const
|
|
146
|
+
export function buildScannerReport(localReport, response, analyzedCount, ecosystems) {
|
|
147
|
+
const packages = ecosystems
|
|
148
|
+
? response.packages.map((pkg) => {
|
|
149
|
+
const ecosystem = ecosystems.get(packageKey(pkg.name, pkg.version));
|
|
150
|
+
return ecosystem ? { ...pkg, ecosystem } : pkg;
|
|
151
|
+
})
|
|
152
|
+
: response.packages;
|
|
153
|
+
const findings = packages
|
|
148
154
|
.filter((pkg) => (pkg.action ?? "pass") !== "pass")
|
|
149
155
|
.map((pkg) => scannerFinding(pkg));
|
|
150
156
|
const warnCount = findings.filter((finding) => finding.severity === "warn").length;
|
|
@@ -163,7 +169,7 @@ export function buildScannerReport(localReport, response, analyzedCount) {
|
|
|
163
169
|
blockCount,
|
|
164
170
|
errorCount: localReport.summary.errorCount
|
|
165
171
|
},
|
|
166
|
-
scanner: response
|
|
172
|
+
scanner: { ...response, packages }
|
|
167
173
|
};
|
|
168
174
|
}
|
|
169
175
|
function scannerFinding(pkg) {
|
package/dist/scan/staged.js
CHANGED
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { createTheme } from "../presentation/theme.js";
|
|
5
5
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
6
|
-
import { loadUserConfig } from "../config/settings.js";
|
|
6
|
+
import { DEFAULT_CONFIG, loadUserConfig } from "../config/settings.js";
|
|
7
7
|
import { offerRememberSync } from "../decisions/remember-prompt.js";
|
|
8
8
|
import { packageKey } from "../decisions/apply.js";
|
|
9
9
|
import { loadDgFile, resolveAcceptedBy, warnUnreadableDgFile } from "../project/dgfile.js";
|
|
@@ -70,16 +70,18 @@ export function runStagedScan(options) {
|
|
|
70
70
|
const env = options.env ?? process.env;
|
|
71
71
|
const cwd = options.cwd ?? process.cwd();
|
|
72
72
|
const theme = createTheme(resolvePresentation().color);
|
|
73
|
-
|
|
73
|
+
// git exports GIT_INDEX_FILE to real pre-commit runs; its absence separates dg's hook self-test from a user commit that inherited the env var.
|
|
74
|
+
if (options.hook && env[GUARD_SELFTEST_ENV] === "1" && env.GIT_INDEX_FILE === undefined) {
|
|
74
75
|
return { exitCode: 2, stdout: "", stderr: "dg guard-commit self-test: synthetic block (exit 2)\n" };
|
|
75
76
|
}
|
|
76
77
|
const root = gitTrimmed(["rev-parse", "--show-toplevel"], { cwd, env });
|
|
77
78
|
if (!root) {
|
|
78
79
|
return notARepoResult();
|
|
79
80
|
}
|
|
81
|
+
const onIncomplete = gitHookOnIncomplete(env);
|
|
80
82
|
const lockfiles = stagedLockfilePaths(cwd, env);
|
|
81
83
|
if (lockfiles === null) {
|
|
82
|
-
return failOpen(theme, "could not read staged changes");
|
|
84
|
+
return failOpen(theme, "could not read staged changes", onIncomplete);
|
|
83
85
|
}
|
|
84
86
|
const scoped = scopeStagedPaths(lockfiles, root, cwd, options.targetPath ?? null);
|
|
85
87
|
if (scoped === null) {
|
|
@@ -96,11 +98,11 @@ export function runStagedScan(options) {
|
|
|
96
98
|
const { dir, count } = materializeStaged(scoped, cwd, env);
|
|
97
99
|
try {
|
|
98
100
|
if (count === 0) {
|
|
99
|
-
return failOpen(theme, "could not read the staged lockfile contents");
|
|
101
|
+
return failOpen(theme, "could not read the staged lockfile contents", onIncomplete);
|
|
100
102
|
}
|
|
101
103
|
const report = tryScannerScan(dir, emptyLocalReport(dir), env, usableDgFile);
|
|
102
104
|
if (!report || !report.scanner) {
|
|
103
|
-
return failOpen(theme, "could not reach the scanner");
|
|
105
|
+
return failOpen(theme, "could not reach the scanner", onIncomplete);
|
|
104
106
|
}
|
|
105
107
|
return decideStagedVerdict(report, env, options.hook, usableDgFile ? { root, file: usableDgFile } : undefined);
|
|
106
108
|
}
|
|
@@ -167,7 +169,7 @@ export function decideStagedVerdict(report, env = process.env, hook = false, dec
|
|
|
167
169
|
return { exitCode: 2, stdout: "", stderr: renderBlock(report.findings, theme) };
|
|
168
170
|
}
|
|
169
171
|
if (action === "error" || action === "unknown") {
|
|
170
|
-
return scannerUnavailable(theme);
|
|
172
|
+
return scannerUnavailable(theme, config.gitHook.onIncomplete);
|
|
171
173
|
}
|
|
172
174
|
if (action === "warn") {
|
|
173
175
|
if (acknowledgedCount > 0 && effective === "pass") {
|
|
@@ -278,14 +280,36 @@ function incompleteNotice(theme, blocked) {
|
|
|
278
280
|
}
|
|
279
281
|
return `${head} ${theme.paint("muted", "— proceeding")}\n`;
|
|
280
282
|
}
|
|
281
|
-
function
|
|
283
|
+
function gitHookOnIncomplete(env) {
|
|
284
|
+
try {
|
|
285
|
+
return loadUserConfig(env).gitHook.onIncomplete;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return DEFAULT_CONFIG.gitHook.onIncomplete;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function failOpen(theme, reason, onIncomplete) {
|
|
292
|
+
if (onIncomplete === "block") {
|
|
293
|
+
return {
|
|
294
|
+
exitCode: 1,
|
|
295
|
+
stdout: "",
|
|
296
|
+
stderr: ` ${theme.paint("unknown", "?")} ${theme.paint("muted", `DG could not verify (${reason}) — commit blocked (gitHook.onIncomplete=block). Use`)} ${theme.paint("accent", "git commit --no-verify")} ${theme.paint("muted", "to override.")}\n`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
282
299
|
return {
|
|
283
300
|
exitCode: 0,
|
|
284
301
|
stdout: "",
|
|
285
|
-
stderr: ` ${theme.paint("unknown", "?")} ${theme.paint("muted", `DG could not verify (${reason}) — commit allowed`)}\n`
|
|
302
|
+
stderr: ` ${theme.paint("unknown", "?")} ${theme.paint("muted", `DG could not verify (${reason}) — commit allowed (gitHook.onIncomplete=allow)`)}\n`
|
|
286
303
|
};
|
|
287
304
|
}
|
|
288
|
-
function scannerUnavailable(theme) {
|
|
305
|
+
function scannerUnavailable(theme, onIncomplete) {
|
|
306
|
+
if (onIncomplete === "block") {
|
|
307
|
+
return {
|
|
308
|
+
exitCode: 1,
|
|
309
|
+
stdout: "",
|
|
310
|
+
stderr: ` ${theme.paint("unknown", "?")} ${theme.paint("muted", "dg: scanner unavailable — staged changes not verified; commit blocked (gitHook.onIncomplete=block). Use")} ${theme.paint("accent", "git commit --no-verify")} ${theme.paint("muted", "to override.")}\n`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
289
313
|
return {
|
|
290
314
|
exitCode: 0,
|
|
291
315
|
stdout: "",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useCallback } from "react";
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
|
+
import { EXIT_ANALYSIS_INCOMPLETE, EXIT_NOTHING_TO_SCAN } from "../commands/types.js";
|
|
4
5
|
import { useScan } from "./hooks/useScan.js";
|
|
5
6
|
import { Spinner } from "./components/Spinner.js";
|
|
6
7
|
import { ProgressBar } from "./components/ProgressBar.js";
|
|
@@ -8,14 +9,15 @@ import { InteractiveResultsView } from "./components/InteractiveResultsView.js";
|
|
|
8
9
|
import { ErrorView } from "./components/ErrorView.js";
|
|
9
10
|
import { ProjectSelector } from "./components/ProjectSelector.js";
|
|
10
11
|
import { SetupBanner } from "./components/SetupBanner.js";
|
|
11
|
-
import {
|
|
12
|
+
import { useResizeRepaint } from "./hooks/useResizeRepaint.js";
|
|
12
13
|
import { effectiveScanAction, scanExitCode } from "./shims.js";
|
|
13
14
|
import { leaveTui, showCursor, tuiIsActive } from "./alt-screen.js";
|
|
14
15
|
import { formatResetDate } from "../install-ui/block-render.js";
|
|
15
|
-
|
|
16
|
+
import { accountHeaderLine } from "./shims.js";
|
|
17
|
+
export const App = ({ config, setupIssues = [], initialView, updateAvailable }) => {
|
|
16
18
|
const { state, scanSelectedProjects, restartSelection } = useScan(config);
|
|
17
19
|
const { exit } = useApp();
|
|
18
|
-
|
|
20
|
+
useResizeRepaint();
|
|
19
21
|
const leaveAltScreen = useCallback(() => {
|
|
20
22
|
if (tuiIsActive()) {
|
|
21
23
|
leaveTui();
|
|
@@ -33,9 +35,6 @@ export const App = ({ config, userStatus, scanUsage, setupIssues = [], initialVi
|
|
|
33
35
|
exit();
|
|
34
36
|
}, [state, config, exit, leaveAltScreen]);
|
|
35
37
|
// Exit alt screen BEFORE writing messages so they appear on the main screen.
|
|
36
|
-
// `delayMs` keeps the alt-screen panel visible for that long before tearing
|
|
37
|
-
// down (default 0 — fire-and-exit). Used by the free_cap_reached path to
|
|
38
|
-
// give the free_cap_reached path time to read the "sign in to unlock" panel.
|
|
39
38
|
const exitWithMessage = useCallback((message, exitCode, delayMs = 0) => {
|
|
40
39
|
process.exitCode = exitCode;
|
|
41
40
|
if (delayMs === 0) {
|
|
@@ -51,11 +50,11 @@ export const App = ({ config, userStatus, scanUsage, setupIssues = [], initialVi
|
|
|
51
50
|
}, [exit, leaveAltScreen]);
|
|
52
51
|
useEffect(() => {
|
|
53
52
|
if (state.phase === "empty") {
|
|
54
|
-
const timer = exitWithMessage(`${state.message}\n`,
|
|
53
|
+
const timer = exitWithMessage(`${state.message}\n`, EXIT_NOTHING_TO_SCAN);
|
|
55
54
|
return () => clearTimeout(timer);
|
|
56
55
|
}
|
|
57
56
|
if (state.phase === "error") {
|
|
58
|
-
const timer = exitWithMessage(`Error: ${state.error.message}\n`,
|
|
57
|
+
const timer = exitWithMessage(`Error: ${state.error.message}\n`, EXIT_ANALYSIS_INCOMPLETE);
|
|
59
58
|
return () => clearTimeout(timer);
|
|
60
59
|
}
|
|
61
60
|
// Reflect the verdict in the process exit code as soon as results render,
|
|
@@ -91,13 +90,13 @@ export const App = ({ config, userStatus, scanUsage, setupIssues = [], initialVi
|
|
|
91
90
|
return _jsx(Spinner, { label: `Found ${state.found} · ${path}` });
|
|
92
91
|
}
|
|
93
92
|
case "selecting":
|
|
94
|
-
return (_jsx(ProjectSelector, { projects: state.projects, onConfirm: scanSelectedProjects, onCancel: () => { process.exitCode = 0; leaveAltScreen(); exit(); }, userStatus:
|
|
93
|
+
return (_jsx(ProjectSelector, { projects: state.projects, onConfirm: scanSelectedProjects, onCancel: () => { process.exitCode = 0; leaveAltScreen(); exit(); }, userStatus: accountHeaderLine() }));
|
|
95
94
|
case "scanning":
|
|
96
95
|
return (_jsx(ProgressBar, { value: state.done, total: state.total, label: state.batchCount > 1 && state.batchIndex >= 1
|
|
97
96
|
? `batch ${state.batchIndex}/${state.batchCount}`
|
|
98
97
|
: undefined }));
|
|
99
98
|
case "results":
|
|
100
|
-
return (_jsx(InteractiveResultsView, { result: state.result, config: config, durationMs: state.durationMs, onExit: handleResultsExit, onBack: restartSelection ?? undefined, discoveredTotal: state.discoveredTotal,
|
|
99
|
+
return (_jsx(InteractiveResultsView, { result: state.result, config: config, durationMs: state.durationMs, onExit: handleResultsExit, onBack: restartSelection ?? undefined, discoveredTotal: state.discoveredTotal, initialView: initialView, decisions: state.decisions }));
|
|
101
100
|
case "empty":
|
|
102
101
|
return _jsx(Text, { dimColor: true, children: state.message });
|
|
103
102
|
case "error":
|
|
@@ -107,9 +106,6 @@ export const App = ({ config, userStatus, scanUsage, setupIssues = [], initialVi
|
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
})();
|
|
110
|
-
// Show the setup banner above the results — but not during empty/error/
|
|
111
|
-
// free_cap_reached phases where it would compete with the message the user
|
|
112
|
-
// actually needs to read.
|
|
113
109
|
const showBanner = state.phase === "selecting" &&
|
|
114
110
|
setupIssues.length > 0;
|
|
115
111
|
// Results fill the terminal height exactly; an extra line there overflows the alt screen.
|
|
@@ -5,10 +5,12 @@ const HIDE_CURSOR = `${ESC}[?25l`;
|
|
|
5
5
|
const SHOW_CURSOR = `${ESC}[?25h`;
|
|
6
6
|
const MOUSE_1000_OFF = `${ESC}[?1000l`;
|
|
7
7
|
const MOUSE_1003_OFF = `${ESC}[?1003l`;
|
|
8
|
-
const CLEAR_HOME = `${ESC}[2J${ESC}[H`;
|
|
8
|
+
export const CLEAR_HOME = `${ESC}[2J${ESC}[H`;
|
|
9
9
|
const CLEAR_LINE = `\r${ESC}[2K`;
|
|
10
10
|
const TITLE = "Dependency Guardian";
|
|
11
11
|
const TITLE_OSC = `${ESC}]0;${TITLE}\x07`;
|
|
12
|
+
const TITLE_PUSH = `${ESC}[22;0t`;
|
|
13
|
+
const TITLE_POP = `${ESC}[23;0t`;
|
|
12
14
|
let tuiActive = false;
|
|
13
15
|
let tuiHandle = null;
|
|
14
16
|
let signalRegistrations = [];
|
|
@@ -43,7 +45,7 @@ function removeRestoreHandlers() {
|
|
|
43
45
|
export function enterTui() {
|
|
44
46
|
if (!process.stdout.isTTY || tuiActive)
|
|
45
47
|
return;
|
|
46
|
-
process.stdout.write(CLEAR_LINE + ALT_SCREEN_ON + HIDE_CURSOR + MOUSE_1000_OFF + MOUSE_1003_OFF + CLEAR_HOME + TITLE_OSC);
|
|
48
|
+
process.stdout.write(CLEAR_LINE + ALT_SCREEN_ON + HIDE_CURSOR + MOUSE_1000_OFF + MOUSE_1003_OFF + CLEAR_HOME + TITLE_PUSH + TITLE_OSC);
|
|
47
49
|
tuiActive = true;
|
|
48
50
|
installRestoreHandlers();
|
|
49
51
|
if (!tuiHandle)
|
|
@@ -56,7 +58,7 @@ export function leaveTui() {
|
|
|
56
58
|
clearInterval(tuiHandle);
|
|
57
59
|
tuiHandle = null;
|
|
58
60
|
}
|
|
59
|
-
process.stdout.write(MOUSE_1003_OFF + MOUSE_1000_OFF + SHOW_CURSOR + ALT_SCREEN_OFF);
|
|
61
|
+
process.stdout.write(MOUSE_1003_OFF + MOUSE_1000_OFF + SHOW_CURSOR + ALT_SCREEN_OFF + TITLE_POP);
|
|
60
62
|
tuiActive = false;
|
|
61
63
|
removeRestoreHandlers();
|
|
62
64
|
}
|
|
@@ -65,11 +67,6 @@ export function clearScreen() {
|
|
|
65
67
|
return;
|
|
66
68
|
process.stdout.write(CLEAR_HOME);
|
|
67
69
|
}
|
|
68
|
-
export function hideCursor() {
|
|
69
|
-
if (!process.stdout.isTTY)
|
|
70
|
-
return;
|
|
71
|
-
process.stdout.write(HIDE_CURSOR);
|
|
72
|
-
}
|
|
73
70
|
export function showCursor() {
|
|
74
71
|
if (!process.stdout.isTTY)
|
|
75
72
|
return;
|