@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/commands/update.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { toolInvocation } from "../util/external-tool.js";
|
|
3
3
|
import { EXIT_USAGE } from "./types.js";
|
|
4
4
|
import { dgVersion } from "./version.js";
|
|
5
5
|
const PACKAGE_NAME = "@westbayberry/dg";
|
|
6
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/u;
|
|
6
7
|
export const updateCommand = {
|
|
7
8
|
name: "update",
|
|
8
9
|
summary: "Check for dg CLI updates.",
|
|
@@ -26,7 +27,7 @@ function runUpdateCommand(args, commandName) {
|
|
|
26
27
|
if (parsed.format === "json") {
|
|
27
28
|
return {
|
|
28
29
|
exitCode: report.status === "unknown" ? 1 : 0,
|
|
29
|
-
stdout: `${JSON.stringify(report, null, 2)}\n`,
|
|
30
|
+
stdout: `${JSON.stringify({ schemaVersion: 1, ...report }, null, 2)}\n`,
|
|
30
31
|
stderr: ""
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -62,17 +63,22 @@ function parseUpdateArgs(args) {
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
export function readLatestVersion(timeoutMs = 5000) {
|
|
65
|
-
const injected = process.env.DG_UPDATE_LATEST_VERSION;
|
|
66
|
+
const injected = process.env.NODE_ENV === "test" ? process.env.DG_UPDATE_LATEST_VERSION : undefined;
|
|
66
67
|
if (injected) {
|
|
67
|
-
return injected;
|
|
68
|
+
return validVersion(injected);
|
|
68
69
|
}
|
|
69
|
-
const
|
|
70
|
+
const invocation = toolInvocation("npm", ["view", PACKAGE_NAME, "version", "--json"]);
|
|
71
|
+
if (!invocation) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const result = spawnSync(invocation.command, [...invocation.args], {
|
|
70
75
|
encoding: "utf8",
|
|
71
76
|
env: {
|
|
72
77
|
...process.env,
|
|
73
78
|
npm_config_ignore_scripts: "true"
|
|
74
79
|
},
|
|
75
|
-
timeout: timeoutMs
|
|
80
|
+
timeout: timeoutMs,
|
|
81
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments
|
|
76
82
|
});
|
|
77
83
|
if (result.status !== 0) {
|
|
78
84
|
return null;
|
|
@@ -83,12 +89,15 @@ export function readLatestVersion(timeoutMs = 5000) {
|
|
|
83
89
|
}
|
|
84
90
|
try {
|
|
85
91
|
const parsed = JSON.parse(raw);
|
|
86
|
-
return typeof parsed === "string" ? parsed : null;
|
|
92
|
+
return typeof parsed === "string" ? validVersion(parsed) : null;
|
|
87
93
|
}
|
|
88
94
|
catch {
|
|
89
|
-
return raw.replace(/^"|"$/gu, "");
|
|
95
|
+
return validVersion(raw.replace(/^"|"$/gu, ""));
|
|
90
96
|
}
|
|
91
97
|
}
|
|
98
|
+
function validVersion(value) {
|
|
99
|
+
return VERSION_PATTERN.test(value) ? value : null;
|
|
100
|
+
}
|
|
92
101
|
function buildUpdateReport(latestVersion) {
|
|
93
102
|
if (!latestVersion) {
|
|
94
103
|
return {
|
package/dist/commands/verify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EXIT_UNAVAILABLE,
|
|
1
|
+
import { EXIT_ANALYSIS_INCOMPLETE, EXIT_UNAVAILABLE, EXIT_USAGE_VERDICT } from "./types.js";
|
|
2
2
|
import { writeFileSync } from "node:fs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
@@ -22,14 +22,14 @@ export const verifyCommand = {
|
|
|
22
22
|
],
|
|
23
23
|
examples: ["dg verify npm:react", "dg verify pypi:requests@2.31.0", "dg verify ./pkg.tgz --verbose", "dg verify package-lock.json --json"],
|
|
24
24
|
details: [
|
|
25
|
-
"dg verify npm:react (or pypi:requests, with an optional @version — defaults to latest) runs a real scanner check on a published package before you install it.
|
|
25
|
+
"dg verify npm:react (or pypi:requests, with an optional @version — defaults to latest) runs a real scanner check on a published package before you install it. Signed-out runs show the verdict and top reasons; dg login unlocks full findings, license info, --json, and --output.",
|
|
26
26
|
"Local paths, workspaces, tgz/zip/wheel artifacts, and lockfiles are verified offline as advisory preflight (free); proxy enforcement remains authoritative for network artifact fetches."
|
|
27
27
|
],
|
|
28
28
|
handler: (context) => {
|
|
29
29
|
const parsed = parseVerifyArgs(context.args);
|
|
30
30
|
if ("error" in parsed) {
|
|
31
31
|
return {
|
|
32
|
-
exitCode:
|
|
32
|
+
exitCode: EXIT_USAGE_VERDICT,
|
|
33
33
|
stdout: "",
|
|
34
34
|
stderr: `dg verify: ${parsed.error}. Usage: dg verify <spec|path|lockfile> [--verbose] [--json|--sarif] [--output <path>]\n`
|
|
35
35
|
};
|
|
@@ -52,7 +52,7 @@ export const verifyCommand = {
|
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
54
|
return {
|
|
55
|
-
exitCode:
|
|
55
|
+
exitCode: EXIT_ANALYSIS_INCOMPLETE,
|
|
56
56
|
stdout: "",
|
|
57
57
|
stderr: `dg verify could not write ${parsed.outputPath}: ${error instanceof Error ? error.message : "unknown write error"}\n`
|
|
58
58
|
};
|
|
@@ -147,5 +147,8 @@ function exitCodeForReport(report) {
|
|
|
147
147
|
if (report.status === "block") {
|
|
148
148
|
return 2;
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
if (report.status === "error") {
|
|
151
|
+
return EXIT_ANALYSIS_INCOMPLETE;
|
|
152
|
+
}
|
|
153
|
+
return report.status === "warn" ? 1 : 0;
|
|
151
154
|
}
|
package/dist/config/settings.js
CHANGED
|
@@ -19,9 +19,7 @@ export const CONFIG_KEYS = Object.freeze([
|
|
|
19
19
|
"cooldown.cargo.age",
|
|
20
20
|
"cooldown.onUnknown",
|
|
21
21
|
"cooldown.exempt",
|
|
22
|
-
"audit.upload"
|
|
23
|
-
"telemetry.enabled",
|
|
24
|
-
"webhooks.enabled"
|
|
22
|
+
"audit.upload"
|
|
25
23
|
]);
|
|
26
24
|
export const DEFAULT_CONFIG = Object.freeze({
|
|
27
25
|
version: 1,
|
|
@@ -55,12 +53,6 @@ export const DEFAULT_CONFIG = Object.freeze({
|
|
|
55
53
|
},
|
|
56
54
|
audit: {
|
|
57
55
|
upload: false
|
|
58
|
-
},
|
|
59
|
-
telemetry: {
|
|
60
|
-
enabled: false
|
|
61
|
-
},
|
|
62
|
-
webhooks: {
|
|
63
|
-
enabled: true
|
|
64
56
|
}
|
|
65
57
|
});
|
|
66
58
|
export class ConfigError extends Error {
|
|
@@ -163,16 +155,18 @@ export function getConfigValue(config, key) {
|
|
|
163
155
|
if (key === "cooldown.exempt") {
|
|
164
156
|
return config.cooldown.exempt;
|
|
165
157
|
}
|
|
166
|
-
|
|
167
|
-
return String(config.audit.upload);
|
|
168
|
-
}
|
|
169
|
-
if (key === "telemetry.enabled") {
|
|
170
|
-
return String(config.telemetry.enabled);
|
|
171
|
-
}
|
|
172
|
-
return String(config.webhooks.enabled);
|
|
158
|
+
return String(config.audit.upload);
|
|
173
159
|
}
|
|
174
|
-
export
|
|
175
|
-
|
|
160
|
+
export const ADVANCED_CONFIG_KEYS = new Set([
|
|
161
|
+
"org.id",
|
|
162
|
+
"policy.scriptHardening",
|
|
163
|
+
"scriptGate.observe",
|
|
164
|
+
"cooldown.npm.age",
|
|
165
|
+
"cooldown.pypi.age",
|
|
166
|
+
"cooldown.cargo.age"
|
|
167
|
+
]);
|
|
168
|
+
export function listConfig(config, includeAdvanced = false) {
|
|
169
|
+
return CONFIG_KEYS.filter((key) => includeAdvanced || !ADVANCED_CONFIG_KEYS.has(key)).map((key) => ({
|
|
176
170
|
key,
|
|
177
171
|
value: getConfigValue(config, key)
|
|
178
172
|
}));
|
|
@@ -266,26 +260,10 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
266
260
|
if (key === "cooldown.exempt") {
|
|
267
261
|
return withCooldown(config, { exempt: parseCooldownExempt(rawValue) });
|
|
268
262
|
}
|
|
269
|
-
if (key === "audit.upload") {
|
|
270
|
-
return {
|
|
271
|
-
...config,
|
|
272
|
-
audit: {
|
|
273
|
-
upload: parseBoolean(rawValue, key)
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
if (key === "telemetry.enabled") {
|
|
278
|
-
return {
|
|
279
|
-
...config,
|
|
280
|
-
telemetry: {
|
|
281
|
-
enabled: parseBoolean(rawValue, key)
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
263
|
return {
|
|
286
264
|
...config,
|
|
287
|
-
|
|
288
|
-
|
|
265
|
+
audit: {
|
|
266
|
+
upload: parseBoolean(rawValue, key)
|
|
289
267
|
}
|
|
290
268
|
};
|
|
291
269
|
}
|
|
@@ -309,8 +287,6 @@ function normalizeConfig(raw) {
|
|
|
309
287
|
const gitHook = fieldObject(raw, "gitHook");
|
|
310
288
|
const cooldown = fieldObject(raw, "cooldown");
|
|
311
289
|
const audit = fieldObject(raw, "audit");
|
|
312
|
-
const telemetry = fieldObject(raw, "telemetry");
|
|
313
|
-
const webhooks = fieldObject(raw, "webhooks");
|
|
314
290
|
const scriptHardening = fieldBoolean(policy, "policy.scriptHardening", "scriptHardening") ?? DEFAULT_CONFIG.policy.scriptHardening;
|
|
315
291
|
return {
|
|
316
292
|
version: 1,
|
|
@@ -344,12 +320,6 @@ function normalizeConfig(raw) {
|
|
|
344
320
|
},
|
|
345
321
|
audit: {
|
|
346
322
|
upload: fieldBoolean(audit, "audit.upload", "upload") ?? DEFAULT_CONFIG.audit.upload
|
|
347
|
-
},
|
|
348
|
-
telemetry: {
|
|
349
|
-
enabled: fieldBoolean(telemetry, "telemetry.enabled", "enabled") ?? DEFAULT_CONFIG.telemetry.enabled
|
|
350
|
-
},
|
|
351
|
-
webhooks: {
|
|
352
|
-
enabled: fieldBoolean(webhooks, "webhooks.enabled", "enabled") ?? DEFAULT_CONFIG.webhooks.enabled
|
|
353
323
|
}
|
|
354
324
|
};
|
|
355
325
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { recordAuditEvent } from "../audit/events.js";
|
|
2
2
|
import { loadUserConfig } from "../config/settings.js";
|
|
3
3
|
import { promptText, promptYesNo } from "../install-ui/prompt.js";
|
|
4
|
-
import {
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { appendDecisions, mutateDgFile } from "../project/dgfile.js";
|
|
5
6
|
import { promptLine as ttyPromptLine, promptYesNo as ttyPromptYesNo } from "../util/tty-prompt.js";
|
|
6
7
|
import { findingFingerprint, packageKey } from "./apply.js";
|
|
7
8
|
const defaultSyncPrompts = {
|
|
@@ -67,7 +68,7 @@ export function persistRemembered(file, packages, options) {
|
|
|
67
68
|
reason: options.reason,
|
|
68
69
|
acceptedBy: options.acceptedBy
|
|
69
70
|
}));
|
|
70
|
-
|
|
71
|
+
mutateDgFile(dirname(file.path), options.env, (current) => appendDecisions(current, additions));
|
|
71
72
|
recordDecisionEvents("decision.accepted", packages.map((pkg) => `${pkg.ecosystem}:${packageKey(pkg.name, pkg.version)}`), options.reason, options.env);
|
|
72
73
|
}
|
|
73
74
|
export function recordDecisionEvents(type, packageNames, reason, env) {
|
|
@@ -86,12 +87,6 @@ export function recordDecisionEvents(type, packageNames, reason, env) {
|
|
|
86
87
|
policyMode,
|
|
87
88
|
createdAt: new Date().toISOString()
|
|
88
89
|
};
|
|
89
|
-
|
|
90
|
-
recordAuditEvent(event, env);
|
|
91
|
-
emitWebhookEvent(event, env);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// audit trail is best-effort; the decision write already succeeded
|
|
95
|
-
}
|
|
90
|
+
recordAuditEvent(event, env);
|
|
96
91
|
}
|
|
97
92
|
}
|
|
@@ -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) {
|
|
@@ -32,7 +32,7 @@ const NEXT_STEP = {
|
|
|
32
32
|
"private-upload-disabled": "Enable private artifact scanning to verify this package.",
|
|
33
33
|
"needs-login": "Run 'dg login' (free) to check packages from the registry before they install.",
|
|
34
34
|
"quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing.",
|
|
35
|
-
cooldown: "Wait
|
|
35
|
+
cooldown: "Wait it out (see holds: dg cooldown), pin an older version, or exempt it: dg cooldown exempt <name>"
|
|
36
36
|
};
|
|
37
37
|
function cooldownDetailLine(cooldown) {
|
|
38
38
|
const window = formatCooldownDuration(cooldown.requiredDays);
|
|
@@ -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
|
+
}
|