@westbayberry/dg 1.0.52 → 1.0.56

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.
Files changed (64) hide show
  1. package/README.md +5 -1
  2. package/dist/index.mjs +349 -168
  3. package/dist/packages/cli/src/alt-screen.js +36 -0
  4. package/dist/packages/cli/src/api.js +322 -0
  5. package/dist/packages/cli/src/auth.js +218 -0
  6. package/dist/packages/cli/src/bin.js +386 -0
  7. package/dist/packages/cli/src/config.js +228 -0
  8. package/dist/packages/cli/src/discover.js +126 -0
  9. package/dist/packages/cli/src/first-run.js +135 -0
  10. package/dist/packages/cli/src/hook.js +360 -0
  11. package/dist/packages/cli/src/lockfile.js +303 -0
  12. package/dist/packages/cli/src/npm-wrapper.js +218 -0
  13. package/dist/packages/cli/src/pip-wrapper.js +273 -0
  14. package/dist/packages/cli/src/sanitize.js +38 -0
  15. package/dist/packages/cli/src/scan-core.js +144 -0
  16. package/dist/packages/cli/src/setup-status.js +46 -0
  17. package/dist/packages/cli/src/static-output.js +625 -0
  18. package/dist/packages/cli/src/telemetry.js +141 -0
  19. package/dist/packages/cli/src/ui/App.js +137 -0
  20. package/dist/packages/cli/src/ui/InitApp.js +391 -0
  21. package/dist/packages/cli/src/ui/LoginApp.js +51 -0
  22. package/dist/packages/cli/src/ui/NpmWrapperApp.js +73 -0
  23. package/dist/packages/cli/src/ui/PipWrapperApp.js +72 -0
  24. package/dist/packages/cli/src/ui/components/ConfirmPrompt.js +24 -0
  25. package/dist/packages/cli/src/ui/components/DemoScanAnimation.js +26 -0
  26. package/dist/packages/cli/src/ui/components/DurationLine.js +7 -0
  27. package/dist/packages/cli/src/ui/components/ErrorView.js +30 -0
  28. package/dist/packages/cli/src/ui/components/FileSavePrompt.js +210 -0
  29. package/dist/packages/cli/src/ui/components/InteractiveResultsView.js +557 -0
  30. package/dist/packages/cli/src/ui/components/Mascot.js +33 -0
  31. package/dist/packages/cli/src/ui/components/ProgressBar.js +51 -0
  32. package/dist/packages/cli/src/ui/components/ProgressDots.js +35 -0
  33. package/dist/packages/cli/src/ui/components/ProjectSelector.js +60 -0
  34. package/dist/packages/cli/src/ui/components/ResultsView.js +105 -0
  35. package/dist/packages/cli/src/ui/components/ScanResultCard.js +54 -0
  36. package/dist/packages/cli/src/ui/components/ScoreHeader.js +142 -0
  37. package/dist/packages/cli/src/ui/components/SetupBanner.js +17 -0
  38. package/dist/packages/cli/src/ui/components/Spinner.js +11 -0
  39. package/dist/packages/cli/src/ui/hooks/useExpandAnimation.js +44 -0
  40. package/dist/packages/cli/src/ui/hooks/useInit.js +341 -0
  41. package/dist/packages/cli/src/ui/hooks/useLogin.js +121 -0
  42. package/dist/packages/cli/src/ui/hooks/useNpmWrapper.js +192 -0
  43. package/dist/packages/cli/src/ui/hooks/usePipWrapper.js +195 -0
  44. package/dist/packages/cli/src/ui/hooks/useScan.js +202 -0
  45. package/dist/packages/cli/src/ui/hooks/useTerminalSize.js +29 -0
  46. package/dist/packages/cli/src/update-check.js +152 -0
  47. package/dist/packages/cli/src/wizard-demo-data.js +63 -0
  48. package/dist/src/ecosystem.js +2 -0
  49. package/dist/src/lockfile/diff.js +38 -0
  50. package/dist/src/lockfile/parse_package_json.js +41 -0
  51. package/dist/src/lockfile/parse_package_lock.js +55 -0
  52. package/dist/src/lockfile/parse_pipfile_lock.js +69 -0
  53. package/dist/src/lockfile/parse_pnpm_lock.js +62 -0
  54. package/dist/src/lockfile/parse_poetry_lock.js +71 -0
  55. package/dist/src/lockfile/parse_requirements.js +83 -0
  56. package/dist/src/lockfile/parse_yarn_lock.js +66 -0
  57. package/dist/src/logger.js +21 -0
  58. package/dist/src/npm/h2pool.js +161 -0
  59. package/dist/src/npm/registry.js +299 -0
  60. package/dist/src/npm/tarball.js +274 -0
  61. package/dist/src/pypi/registry.js +299 -0
  62. package/dist/src/pypi/tarball.js +361 -0
  63. package/dist/src/types.js +2 -0
  64. package/package.json +6 -3
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfirmPrompt = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const ink_1 = require("ink");
9
+ const ink_2 = require("ink");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const ConfirmPrompt = ({ message, onConfirm, onReject, }) => {
12
+ (0, ink_2.useInput)((input, key) => {
13
+ if (input.toLowerCase() === "y") {
14
+ onConfirm();
15
+ }
16
+ else if (input.toLowerCase() === "n" ||
17
+ key.return ||
18
+ key.escape) {
19
+ onReject();
20
+ }
21
+ });
22
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.red.bold("\u26A0"), " ", chalk_1.default.bold(message)] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Install anyway? [", chalk_1.default.bold.green("y"), "es / ", chalk_1.default.bold.red("N"), "o]"] })] }));
23
+ };
24
+ exports.ConfirmPrompt = ConfirmPrompt;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DemoScanAnimation = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const Spinner_1 = require("./Spinner");
7
+ const wizard_demo_data_1 = require("../../wizard-demo-data");
8
+ const DemoScanAnimation = ({ onComplete }) => {
9
+ const [labelIdx, setLabelIdx] = (0, react_1.useState)(0);
10
+ (0, react_1.useEffect)(() => {
11
+ const tickMs = Math.floor(wizard_demo_data_1.DEMO_SCAN_TOTAL_MS / wizard_demo_data_1.DEMO_SCAN_LABELS.length);
12
+ const interval = setInterval(() => {
13
+ setLabelIdx((i) => {
14
+ if (i + 1 >= wizard_demo_data_1.DEMO_SCAN_LABELS.length) {
15
+ clearInterval(interval);
16
+ setTimeout(onComplete, tickMs);
17
+ return i;
18
+ }
19
+ return i + 1;
20
+ });
21
+ }, tickMs);
22
+ return () => clearInterval(interval);
23
+ }, [onComplete]);
24
+ return (0, jsx_runtime_1.jsx)(Spinner_1.Spinner, { label: wizard_demo_data_1.DEMO_SCAN_LABELS[labelIdx] });
25
+ };
26
+ exports.DemoScanAnimation = DemoScanAnimation;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DurationLine = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ink_1 = require("ink");
6
+ const DurationLine = ({ ms }) => ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: ["Completed in ", (ms / 1000).toFixed(1), "s"] }));
7
+ exports.DurationLine = DurationLine;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ErrorView = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const ink_1 = require("ink");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const sanitize_1 = require("../../sanitize");
11
+ function getHint(error) {
12
+ const statusCode = error.statusCode;
13
+ if (typeof statusCode !== "number")
14
+ return null;
15
+ switch (statusCode) {
16
+ case 401:
17
+ return "Not authenticated. Run `dg login` to sign in.";
18
+ case 429:
19
+ return "Rate limit exceeded. Upgrade at westbayberry.com/pricing";
20
+ case 504:
21
+ return "Server timeout. Try scanning fewer packages.";
22
+ default:
23
+ return null;
24
+ }
25
+ }
26
+ const ErrorView = ({ error }) => {
27
+ const hint = getHint(error);
28
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingLeft: 1, paddingRight: 1, children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { children: chalk_1.default.red.bold("\u2718 Error") }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: (0, sanitize_1.sanitize)(error.message) }), hint && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.yellow("\u2192"), " ", hint] }))] }));
29
+ };
30
+ exports.ErrorView = ErrorView;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FileSavePrompt = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const ink_1 = require("ink");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const node_fs_1 = require("node:fs");
12
+ const node_path_1 = require("node:path");
13
+ // ── Constants ───────────────────────────────────────────────
14
+ const VISIBLE_ROWS = 15;
15
+ // ── Helpers ─────────────────────────────────────────────────
16
+ function listDirectory(dir) {
17
+ try {
18
+ const raw = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
19
+ const entries = raw
20
+ .map((e) => ({ name: e.name, isDirectory: e.isDirectory() }))
21
+ .sort((a, b) => {
22
+ if (a.isDirectory !== b.isDirectory)
23
+ return a.isDirectory ? -1 : 1;
24
+ return a.name.localeCompare(b.name);
25
+ });
26
+ // Add ".." unless at filesystem root
27
+ if ((0, node_path_1.dirname)(dir) !== dir) {
28
+ entries.unshift({ name: "..", isDirectory: true });
29
+ }
30
+ return entries;
31
+ }
32
+ catch {
33
+ return [{ name: "..", isDirectory: true }];
34
+ }
35
+ }
36
+ function ensureJsonExtension(name) {
37
+ if (!name)
38
+ return "scan.json";
39
+ if (name.endsWith(".json"))
40
+ return name;
41
+ return name + ".json";
42
+ }
43
+ function clamp(n, min, max) {
44
+ return Math.max(min, Math.min(max, n));
45
+ }
46
+ // ── Reducer ─────────────────────────────────────────────────
47
+ function reducer(state, action) {
48
+ switch (action.type) {
49
+ case "SET_FILENAME":
50
+ return { ...state, filename: action.filename, cursorPos: action.cursorPos, focus: "filename", confirmOverwrite: false, error: null };
51
+ case "EDIT_FILENAME":
52
+ return { ...state, filename: action.filename, cursorPos: action.cursorPos, confirmOverwrite: false, error: null };
53
+ case "SET_DIRECTORY":
54
+ return { ...state, directory: action.directory, entries: action.entries, browserCursor: 0, browserViewport: 0, filterText: "", error: null };
55
+ case "MOVE_BROWSER": {
56
+ let viewport = state.browserViewport;
57
+ if (action.cursor < viewport)
58
+ viewport = action.cursor;
59
+ if (action.cursor >= viewport + VISIBLE_ROWS)
60
+ viewport = action.cursor - VISIBLE_ROWS + 1;
61
+ return { ...state, browserCursor: action.cursor, browserViewport: viewport };
62
+ }
63
+ case "FOCUS":
64
+ return { ...state, focus: action.target, filterText: action.target === "filename" ? "" : state.filterText };
65
+ case "FILTER":
66
+ return { ...state, filterText: action.text, browserCursor: 0, browserViewport: 0 };
67
+ case "CONFIRM_OVERWRITE":
68
+ return { ...state, confirmOverwrite: true };
69
+ case "RESET_OVERWRITE":
70
+ return { ...state, confirmOverwrite: false };
71
+ case "SET_ERROR":
72
+ return { ...state, error: action.error };
73
+ }
74
+ }
75
+ // ── Component ───────────────────────────────────────────────
76
+ const FileSavePrompt = ({ defaultName, directory: initialDir, score, action: scanAction, packageCount, json, onSaved, onCancel, }) => {
77
+ const initialEntries = (0, react_1.useMemo)(() => listDirectory(initialDir), [initialDir]);
78
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, {
79
+ filename: defaultName,
80
+ cursorPos: defaultName.length,
81
+ directory: initialDir,
82
+ entries: initialEntries,
83
+ browserCursor: 0,
84
+ browserViewport: 0,
85
+ focus: "filename",
86
+ filterText: "",
87
+ confirmOverwrite: false,
88
+ error: null,
89
+ });
90
+ const filteredEntries = (0, react_1.useMemo)(() => {
91
+ // Only show directories — user is picking a save location, not opening a file
92
+ const dirs = state.entries.filter((e) => e.isDirectory);
93
+ if (!state.filterText)
94
+ return dirs;
95
+ const q = state.filterText.toLowerCase();
96
+ return dirs.filter((e) => e.name.toLowerCase().includes(q));
97
+ }, [state.entries, state.filterText]);
98
+ const visibleEntries = filteredEntries.slice(state.browserViewport, state.browserViewport + VISIBLE_ROWS);
99
+ (0, ink_1.useInput)((input, key) => {
100
+ // Escape always cancels
101
+ if (key.escape) {
102
+ onCancel();
103
+ return;
104
+ }
105
+ // ── Filename focus ──
106
+ if (state.focus === "filename") {
107
+ if (key.return) {
108
+ const fullName = ensureJsonExtension(state.filename);
109
+ const fullPath = (0, node_path_1.join)(state.directory, fullName);
110
+ if (!state.confirmOverwrite && (0, node_fs_1.existsSync)(fullPath)) {
111
+ dispatch({ type: "CONFIRM_OVERWRITE" });
112
+ return;
113
+ }
114
+ // Write synchronously — if it fails, surface the error in the TUI
115
+ // instead of silently reporting success (the old behavior).
116
+ try {
117
+ (0, node_fs_1.writeFileSync)(fullPath, json + "\n");
118
+ }
119
+ catch (err) {
120
+ const msg = err instanceof Error ? err.message : String(err);
121
+ dispatch({ type: "SET_ERROR", error: `Write failed: ${msg}` });
122
+ return;
123
+ }
124
+ onSaved(fullPath);
125
+ return;
126
+ }
127
+ if (key.downArrow) {
128
+ dispatch({ type: "FOCUS", target: "browser" });
129
+ return;
130
+ }
131
+ if (key.backspace || key.delete) {
132
+ if (state.cursorPos > 0) {
133
+ const next = state.filename.slice(0, state.cursorPos - 1) + state.filename.slice(state.cursorPos);
134
+ dispatch({ type: "EDIT_FILENAME", filename: next, cursorPos: state.cursorPos - 1 });
135
+ }
136
+ return;
137
+ }
138
+ if (key.leftArrow) {
139
+ dispatch({ type: "EDIT_FILENAME", filename: state.filename, cursorPos: Math.max(0, state.cursorPos - 1) });
140
+ return;
141
+ }
142
+ if (key.rightArrow) {
143
+ dispatch({ type: "EDIT_FILENAME", filename: state.filename, cursorPos: Math.min(state.filename.length, state.cursorPos + 1) });
144
+ return;
145
+ }
146
+ if (input && /^[\x20-\x7e]+$/.test(input)) {
147
+ const next = state.filename.slice(0, state.cursorPos) + input + state.filename.slice(state.cursorPos);
148
+ dispatch({ type: "EDIT_FILENAME", filename: next, cursorPos: state.cursorPos + input.length });
149
+ return;
150
+ }
151
+ return;
152
+ }
153
+ // ── Browser focus ──
154
+ if (key.upArrow) {
155
+ if (state.browserCursor > 0) {
156
+ dispatch({ type: "MOVE_BROWSER", cursor: state.browserCursor - 1 });
157
+ }
158
+ else {
159
+ // Already at top — go back to filename editor
160
+ dispatch({ type: "FOCUS", target: "filename" });
161
+ }
162
+ return;
163
+ }
164
+ // Backspace in browser with no filter text → navigate up (like "..")
165
+ if ((key.backspace || key.delete) && !state.filterText) {
166
+ const parentDir = path.dirname(state.directory);
167
+ if (parentDir !== state.directory) {
168
+ dispatch({ type: "SET_DIRECTORY", directory: parentDir, entries: listDirectory(parentDir) });
169
+ }
170
+ return;
171
+ }
172
+ if (key.downArrow) {
173
+ dispatch({ type: "MOVE_BROWSER", cursor: clamp(state.browserCursor + 1, 0, filteredEntries.length - 1) });
174
+ return;
175
+ }
176
+ if (key.return) {
177
+ const entry = filteredEntries[state.browserCursor];
178
+ if (!entry || !entry.isDirectory)
179
+ return;
180
+ const newDir = entry.name === ".."
181
+ ? path.dirname(state.directory)
182
+ : path.join(state.directory, entry.name);
183
+ dispatch({ type: "SET_DIRECTORY", directory: newDir, entries: listDirectory(newDir) });
184
+ return;
185
+ }
186
+ // Type-to-filter
187
+ if (key.backspace || key.delete) {
188
+ dispatch({ type: "FILTER", text: state.filterText.slice(0, -1) });
189
+ return;
190
+ }
191
+ if (input && /^[\x20-\x7e]+$/.test(input)) {
192
+ dispatch({ type: "FILTER", text: state.filterText + input });
193
+ }
194
+ });
195
+ // ── Render ──
196
+ const actionColor = scanAction === "block" ? chalk_1.default.red : scanAction === "warn" ? chalk_1.default.yellow : chalk_1.default.green;
197
+ const filenameDisplay = state.filename.slice(0, state.cursorPos) +
198
+ (state.focus === "filename" ? chalk_1.default.inverse(state.filename[state.cursorPos] || " ") : "") +
199
+ state.filename.slice(state.cursorPos + 1);
200
+ const shortDir = state.directory.replace(process.env.HOME || "", "~");
201
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", paddingLeft: 1, children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.cyan("\u25C6"), " ", chalk_1.default.bold("Save Scan Results")] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Score: ", chalk_1.default.bold(String(score)), " ", actionColor(scanAction.toUpperCase()), " ", chalk_1.default.dim("\u2502"), " ", packageCount, " packages scanned"] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [state.focus === "filename" ? chalk_1.default.cyan("\u258C") : " ", chalk_1.default.bold("File:"), " ", filenameDisplay, chalk_1.default.dim(".json")] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: chalk_1.default.dim(` \u2500\u2500\u2500 ${shortDir} \u2500\u2500\u2500 [${state.browserCursor + 1}/${filteredEntries.length}]`) }), visibleEntries.map((entry, i) => {
202
+ const realIdx = state.browserViewport + i;
203
+ const isCursor = state.focus === "browser" && realIdx === state.browserCursor;
204
+ const prefix = isCursor ? chalk_1.default.cyan("\u258C") : " ";
205
+ const icon = entry.isDirectory ? "\uD83D\uDCC1 " : " ";
206
+ const name = entry.isDirectory ? chalk_1.default.bold(entry.name + "/") : entry.name;
207
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [prefix, icon, name] }, `${entry.name}-${i}`));
208
+ }), filteredEntries.length > VISIBLE_ROWS && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", chalk_1.default.dim(`\u2191\u2193 ${filteredEntries.length - VISIBLE_ROWS} more`)] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), state.filterText && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" filter: \"", state.filterText, "\" (", filteredEntries.length, " matches)"] })), state.confirmOverwrite && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: "yellow", children: [" ", chalk_1.default.yellow("\u26A0"), " ", ensureJsonExtension(state.filename), " exists ", chalk_1.default.dim("\u2014"), " Enter again to overwrite"] })), state.error && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: "red", children: [" ", chalk_1.default.red("\u2717"), " ", state.error] })), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.bold.hex("#FFD700")("\u23CE"), " ", chalk_1.default.bold.hex("#FFD700")("save"), " ", chalk_1.default.bold.cyan("\u2191\u2193"), " ", chalk_1.default.dim("browse"), " ", chalk_1.default.bold.cyan("esc"), " ", chalk_1.default.dim("cancel")] })] }));
209
+ };
210
+ exports.FileSavePrompt = FileSavePrompt;