@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
6
|
+
import { basename, join, resolve, sep } from "node:path";
|
|
7
|
+
import { exportDestinations, resolveExportPath, userHomeDir, writeReportAtomic } from "../util/report-writer.js";
|
|
8
|
+
export function loginRequiredToast() {
|
|
9
|
+
return `${chalk.yellow("Sign in to export:")} ${chalk.cyan.bold("dg login")} ${chalk.dim("(free account)")}`;
|
|
10
|
+
}
|
|
11
|
+
const PATH_DISPLAY_MAX = 48;
|
|
12
|
+
function cursorBeforeExtension(name) {
|
|
13
|
+
const slash = name.lastIndexOf("/");
|
|
14
|
+
const dot = name.indexOf(".", slash + 2);
|
|
15
|
+
return dot > slash + 1 ? dot : name.length;
|
|
16
|
+
}
|
|
17
|
+
function fieldFor(name) {
|
|
18
|
+
return { value: name, cursor: cursorBeforeExtension(name) };
|
|
19
|
+
}
|
|
20
|
+
function abbreviateHome(dir, home) {
|
|
21
|
+
if (dir === home)
|
|
22
|
+
return "~";
|
|
23
|
+
if (dir.startsWith(home + sep))
|
|
24
|
+
return `~${dir.slice(home.length)}`;
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
function truncatePath(text) {
|
|
28
|
+
if (text.length <= PATH_DISPLAY_MAX)
|
|
29
|
+
return text;
|
|
30
|
+
return `…${text.slice(text.length - PATH_DISPLAY_MAX + 1)}`;
|
|
31
|
+
}
|
|
32
|
+
function prefillFor(dir, cwd, home, filename) {
|
|
33
|
+
if (resolve(dir) === resolve(cwd))
|
|
34
|
+
return filename;
|
|
35
|
+
if (dir === home)
|
|
36
|
+
return `~/${filename}`;
|
|
37
|
+
if (dir.startsWith(home + sep))
|
|
38
|
+
return `~${dir.slice(home.length)}/${filename}`;
|
|
39
|
+
return join(dir, filename);
|
|
40
|
+
}
|
|
41
|
+
function completePath(value, cwd) {
|
|
42
|
+
const slash = value.lastIndexOf("/");
|
|
43
|
+
const dir = slash < 0 ? "" : value.slice(0, slash + 1);
|
|
44
|
+
const fragment = slash < 0 ? value : value.slice(slash + 1);
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = readdirSync(resolveExportPath(dir === "" ? "." : dir, cwd), { withFileTypes: true })
|
|
48
|
+
.map((entry) => ({ name: entry.name, isDir: entry.isDirectory() }));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
const match = entries
|
|
54
|
+
.filter((entry) => entry.name.toLowerCase().startsWith(fragment.toLowerCase()))
|
|
55
|
+
.sort((a, b) => (a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1))[0];
|
|
56
|
+
if (!match)
|
|
57
|
+
return value;
|
|
58
|
+
return `${dir}${match.name}${match.isDir ? "/" : ""}`;
|
|
59
|
+
}
|
|
60
|
+
export const ExportDialog = ({ options, theme, cwd, onDone, env = process.env }) => {
|
|
61
|
+
const multi = options.length > 1;
|
|
62
|
+
const [stage, setStage] = useState(multi ? "format" : "dest");
|
|
63
|
+
const [selected, setSelected] = useState(0);
|
|
64
|
+
const [destIndex, setDestIndex] = useState(0);
|
|
65
|
+
const [field, setField] = useState({ value: "", cursor: 0 });
|
|
66
|
+
const home = useMemo(() => userHomeDir(env), [env]);
|
|
67
|
+
const destinations = useMemo(() => exportDestinations(cwd, env), [cwd, env]);
|
|
68
|
+
const option = options[selected];
|
|
69
|
+
const filename = basename(option?.defaultName ?? "");
|
|
70
|
+
const rowCount = destinations.length + 1;
|
|
71
|
+
const writeTo = (target) => {
|
|
72
|
+
if (!option)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
writeReportAtomic(target, option.render());
|
|
76
|
+
onDone({ path: target });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
onDone({ error: error instanceof Error ? error.message : "write failed" });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const openEditor = (dest) => {
|
|
83
|
+
const prefill = dest ? prefillFor(dest.dir, cwd, home, filename) : filename;
|
|
84
|
+
setField(fieldFor(prefill));
|
|
85
|
+
setStage("path");
|
|
86
|
+
};
|
|
87
|
+
const insertAtCursor = (text) => {
|
|
88
|
+
setField((f) => ({ value: f.value.slice(0, f.cursor) + text + f.value.slice(f.cursor), cursor: f.cursor + text.length }));
|
|
89
|
+
};
|
|
90
|
+
const moveCursor = (to) => {
|
|
91
|
+
setField((f) => ({ ...f, cursor: Math.max(0, Math.min(f.value.length, to(f))) }));
|
|
92
|
+
};
|
|
93
|
+
useInput((input, key) => {
|
|
94
|
+
if (stage === "format") {
|
|
95
|
+
if (key.escape || input === "q") {
|
|
96
|
+
onDone(null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (key.return) {
|
|
100
|
+
setStage("dest");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (key.upArrow || input === "k") {
|
|
104
|
+
setSelected((s) => Math.max(0, s - 1));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (key.downArrow || input === "j") {
|
|
108
|
+
setSelected((s) => Math.min(options.length - 1, s + 1));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (stage === "dest") {
|
|
114
|
+
if (key.escape) {
|
|
115
|
+
if (multi)
|
|
116
|
+
setStage("format");
|
|
117
|
+
else
|
|
118
|
+
onDone(null);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (input === "t") {
|
|
122
|
+
openEditor(destinations[destIndex]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (key.upArrow || input === "k") {
|
|
126
|
+
setDestIndex((d) => Math.max(0, d - 1));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (key.downArrow || input === "j") {
|
|
130
|
+
setDestIndex((d) => Math.min(rowCount - 1, d + 1));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (key.return) {
|
|
134
|
+
const dest = destinations[destIndex];
|
|
135
|
+
if (dest)
|
|
136
|
+
writeTo(join(dest.dir, filename));
|
|
137
|
+
else
|
|
138
|
+
openEditor(destinations[destIndex - 1] ?? destinations[0]);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (key.escape) {
|
|
143
|
+
setStage("dest");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (key.return) {
|
|
147
|
+
const trimmed = field.value.trim();
|
|
148
|
+
if (trimmed.length > 0)
|
|
149
|
+
writeTo(resolveExportPath(trimmed, cwd));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (key.tab) {
|
|
153
|
+
setField((f) => fieldFor(completePath(f.value, cwd)));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (key.leftArrow) {
|
|
157
|
+
moveCursor((f) => f.cursor - 1);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (key.rightArrow) {
|
|
161
|
+
moveCursor((f) => f.cursor + 1);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (key.ctrl && input === "a") {
|
|
165
|
+
moveCursor(() => 0);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (key.ctrl && input === "e") {
|
|
169
|
+
moveCursor((f) => f.value.length);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (key.delete || key.backspace) {
|
|
173
|
+
setField((f) => (f.cursor === 0 ? f : { value: f.value.slice(0, f.cursor - 1) + f.value.slice(f.cursor), cursor: f.cursor - 1 }));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (input && !key.ctrl && !key.meta && /^[\x20-\x7e]+$/.test(input)) {
|
|
177
|
+
insertAtCursor(input);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
const target = field.value.trim().length > 0 ? resolveExportPath(field.value, cwd) : null;
|
|
181
|
+
const targetExists = target !== null && existsSync(target);
|
|
182
|
+
const beforeCursor = field.value.slice(0, field.cursor);
|
|
183
|
+
const atCursor = field.value.charAt(field.cursor);
|
|
184
|
+
const afterCursor = field.value.slice(field.cursor + 1);
|
|
185
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, children: [_jsx(Text, { bold: true, children: "Export" }), _jsx(Text, { children: "" }), stage === "format" && options.map((opt, idx) => {
|
|
186
|
+
const isSelected = idx === selected;
|
|
187
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: 5, flexShrink: 0, children: _jsxs(Text, { children: [" ", isSelected ? chalk.cyan("●") : chalk.dim("○")] }) }), _jsx(Box, { flexShrink: 1, children: _jsx(Text, { wrap: "truncate-end", children: isSelected ? chalk.bold(opt.label) : opt.label }) })] }, opt.label));
|
|
188
|
+
}), stage === "dest" && (_jsxs(_Fragment, { children: [destinations.map((dest, idx) => {
|
|
189
|
+
const isSelected = idx === destIndex;
|
|
190
|
+
const replaces = existsSync(join(dest.dir, filename));
|
|
191
|
+
const shown = truncatePath(`${abbreviateHome(dest.dir, home)}/${filename}`.replace(/\/+/g, "/"));
|
|
192
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsxs(Text, { children: [" ", isSelected ? chalk.cyan("▸") : " "] }) }), _jsx(Box, { width: 14, flexShrink: 0, children: _jsx(Text, { children: isSelected ? chalk.bold(dest.label) : dest.label }) }), _jsx(Box, { flexShrink: 1, children: _jsxs(Text, { wrap: "truncate-start", children: [theme.paint("muted", shown), replaces ? theme.paint("warn", " (replaces)") : ""] }) })] }, dest.label));
|
|
193
|
+
}), _jsxs(Box, { children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsxs(Text, { children: [" ", destIndex === destinations.length ? chalk.cyan("▸") : " "] }) }), _jsx(Box, { flexShrink: 1, children: _jsx(Text, { children: destIndex === destinations.length ? chalk.bold("Type a path…") : theme.paint("muted", "Type a path…") }) })] })] })), stage === "path" && (_jsxs(_Fragment, { children: [option && _jsx(Text, { dimColor: true, wrap: "truncate-end", children: option.label }), _jsxs(Text, { wrap: "truncate-start", children: [chalk.cyan("▌ "), chalk.bold("Save as"), " ", beforeCursor, atCursor === "" ? chalk.cyan("█") : chalk.inverse(atCursor) + afterCursor] }), _jsx(Text, { wrap: "truncate-start", children: target === null
|
|
194
|
+
? theme.paint("muted", "enter a file name")
|
|
195
|
+
: targetExists
|
|
196
|
+
? theme.paint("warn", `${target} exists — Enter overwrites`)
|
|
197
|
+
: theme.paint("muted", `→ ${target}`) })] }))] }), _jsxs(Text, { children: [" ", stage === "format" ? (_jsxs(_Fragment, { children: [chalk.bold.cyan("↑↓"), " ", chalk.dim("choose"), " ", chalk.bold.cyan("⏎"), " ", chalk.dim("next"), " ", chalk.bold.cyan("Esc"), " ", chalk.dim("cancel")] })) : stage === "dest" ? (_jsxs(_Fragment, { children: [chalk.bold.cyan("↑↓"), " ", chalk.dim("choose"), " ", chalk.bold.cyan("⏎"), " ", chalk.dim(`save ${filename}`), " ", chalk.bold.cyan("t"), " ", chalk.dim("type path"), " ", chalk.bold.cyan("Esc"), " ", chalk.dim(multi ? "back" : "cancel")] })) : (_jsxs(_Fragment, { children: [chalk.bold.cyan("⏎"), " ", chalk.dim("save"), " ", chalk.bold.cyan("Tab"), " ", chalk.dim("complete"), " ", chalk.bold.cyan("Esc"), " ", chalk.dim("back")] }))] })] }));
|
|
198
|
+
};
|
|
@@ -13,8 +13,8 @@ export const LiveInstall = ({ view }) => {
|
|
|
13
13
|
if (view.phase === "scanning") {
|
|
14
14
|
return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "cyan", children: _jsx(InkSpinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", view.total === 0
|
|
15
15
|
? "DG starting protection…"
|
|
16
|
-
: view.resolvedTotal !== undefined
|
|
17
|
-
? `DG verifying ${view.total}/${
|
|
16
|
+
: view.resolvedTotal !== undefined && view.total <= view.resolvedTotal
|
|
17
|
+
? `DG verifying ${view.total}/${view.resolvedTotal}…`
|
|
18
18
|
: `DG verifying ${packageCount(view.total)}…`] }), view.current ? _jsxs(Text, { dimColor: true, children: [" ", view.current] }) : null] }));
|
|
19
19
|
}
|
|
20
20
|
if (view.total === 0 && !view.blocked) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatCooldownDuration, formatPackageAge } from "../policy/cooldown.js";
|
|
1
2
|
const VERIFIED_BAD = new Set([
|
|
2
3
|
"malware",
|
|
3
4
|
"policy",
|
|
@@ -19,6 +20,7 @@ const HEADLINES = {
|
|
|
19
20
|
"api-timeout": "scanner timed out",
|
|
20
21
|
"registry-timeout": "registry timed out",
|
|
21
22
|
"analysis-incomplete": "analysis incomplete",
|
|
23
|
+
cooldown: "release too new (cooldown)",
|
|
22
24
|
"unsupported-manager": "unsupported package manager",
|
|
23
25
|
"proxy-setup-failure": "protection unavailable"
|
|
24
26
|
};
|
|
@@ -29,14 +31,24 @@ const NEXT_STEP = {
|
|
|
29
31
|
"hash-mismatch": "Clear your package cache and retry. If it persists, do not install.",
|
|
30
32
|
"private-upload-disabled": "Enable private artifact scanning to verify this package.",
|
|
31
33
|
"needs-login": "Run 'dg login' (free) to check packages from the registry before they install.",
|
|
32
|
-
"quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing."
|
|
34
|
+
"quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing.",
|
|
35
|
+
cooldown: "Wait it out (see holds: dg cooldown), pin an older version, or exempt it: dg cooldown exempt <name>"
|
|
33
36
|
};
|
|
37
|
+
function cooldownDetailLine(cooldown) {
|
|
38
|
+
const window = formatCooldownDuration(cooldown.requiredDays);
|
|
39
|
+
const eligible = formatResetDate(cooldown.eligibleAt);
|
|
40
|
+
const suffix = eligible ? ` (eligible ${eligible})` : "";
|
|
41
|
+
if (cooldown.ageDays === undefined) {
|
|
42
|
+
return `publish time unknown; your cooldown is ${window}${suffix}`;
|
|
43
|
+
}
|
|
44
|
+
return `published ${formatPackageAge(cooldown.ageDays)}; your cooldown is ${window}${suffix}`;
|
|
45
|
+
}
|
|
34
46
|
export function describeBlockedInstall(decision) {
|
|
35
47
|
const verifiedBad = VERIFIED_BAD.has(decision.cause);
|
|
36
48
|
const override = decision.forceOverride && !decision.forceOverride.allowed
|
|
37
49
|
? "not allowed by your policy"
|
|
38
50
|
: "re-run with --dg-force-install";
|
|
39
|
-
const nextStep = verifiedBad || decision.cause === "needs-login"
|
|
51
|
+
const nextStep = verifiedBad || decision.cause === "needs-login" || decision.cause === "cooldown"
|
|
40
52
|
? NEXT_STEP[decision.cause]
|
|
41
53
|
: "Re-check later with 'dg verify', or override if you accept the risk.";
|
|
42
54
|
return {
|
|
@@ -85,9 +97,14 @@ export function renderInstallDecision(decision) {
|
|
|
85
97
|
const lines = [
|
|
86
98
|
verifiedBad
|
|
87
99
|
? `✘ DG blocked install — ${headline}`
|
|
88
|
-
:
|
|
100
|
+
: decision.cause === "cooldown"
|
|
101
|
+
? `? DG quarantined ${decision.packageName} — ${headline}`
|
|
102
|
+
: `? DG could not verify ${decision.packageName} — ${headline}`,
|
|
89
103
|
` ${decision.packageName} ${decision.reason}`
|
|
90
104
|
];
|
|
105
|
+
if (decision.cause === "cooldown" && decision.cooldown) {
|
|
106
|
+
lines.push(` ${cooldownDetailLine(decision.cooldown)}`);
|
|
107
|
+
}
|
|
91
108
|
if (decision.dashboardUrl) {
|
|
92
109
|
lines.push(` Evidence: ${decision.dashboardUrl}`);
|
|
93
110
|
}
|
|
@@ -97,7 +114,7 @@ export function renderInstallDecision(decision) {
|
|
|
97
114
|
lines.push(decision.forceOverride && !decision.forceOverride.allowed
|
|
98
115
|
? " Override: not allowed by your policy"
|
|
99
116
|
: " Override: re-run with --dg-force-install");
|
|
100
|
-
const next = verifiedBad || decision.cause === "needs-login"
|
|
117
|
+
const next = verifiedBad || decision.cause === "needs-login" || decision.cause === "cooldown"
|
|
101
118
|
? NEXT_STEP[decision.cause]
|
|
102
119
|
: "Re-check later with 'dg verify', or override if you accept the risk.";
|
|
103
120
|
if (next) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolvePresentation } from "../presentation/mode.js";
|
|
2
|
+
import { createTheme } from "../presentation/theme.js";
|
|
3
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
4
|
+
const FRAME_INTERVAL_MS = 80;
|
|
5
|
+
const CLEAR_LINE = "\r\u001b[2K";
|
|
6
|
+
const INACTIVE = { stop: () => undefined };
|
|
7
|
+
export function startPrepSpinner(label, stream = process.stderr, env = process.env) {
|
|
8
|
+
const presentation = resolvePresentation({ stream, env });
|
|
9
|
+
if (presentation.mode !== "rich") {
|
|
10
|
+
return INACTIVE;
|
|
11
|
+
}
|
|
12
|
+
const theme = createTheme(presentation.color);
|
|
13
|
+
let frame = 0;
|
|
14
|
+
const draw = () => {
|
|
15
|
+
stream.write(`\r ${theme.paint("unknown", FRAMES[frame % FRAMES.length] ?? "")} ${label}`);
|
|
16
|
+
frame += 1;
|
|
17
|
+
};
|
|
18
|
+
draw();
|
|
19
|
+
const timer = setInterval(draw, FRAME_INTERVAL_MS);
|
|
20
|
+
timer.unref();
|
|
21
|
+
let stopped = false;
|
|
22
|
+
return {
|
|
23
|
+
stop: () => {
|
|
24
|
+
if (stopped) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
stopped = true;
|
|
28
|
+
clearInterval(timer);
|
|
29
|
+
stream.write(CLEAR_LINE);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -6,6 +6,20 @@ export function defaultPromptIo() {
|
|
|
6
6
|
isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY)
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
+
export async function promptText(question, io) {
|
|
10
|
+
if (!io.isTTY) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const rl = createInterface({ input: io.input, output: io.output });
|
|
14
|
+
try {
|
|
15
|
+
return await new Promise((resolve) => {
|
|
16
|
+
rl.question(question, resolve);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
rl.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
9
23
|
export async function promptYesNo(question, io, defaultYes = false) {
|
|
10
24
|
if (!io.isTTY) {
|
|
11
25
|
return false;
|