@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,341 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._initialState = _initialState;
4
+ exports._initialStateFirstRun = _initialStateFirstRun;
5
+ exports._reducer = _reducer;
6
+ exports.canGoBack = canGoBack;
7
+ exports.useInit = useInit;
8
+ const react_1 = require("react");
9
+ const discover_1 = require("../../discover");
10
+ const hook_1 = require("../../hook");
11
+ const scan_core_1 = require("../../scan-core");
12
+ const config_1 = require("../../config");
13
+ function _initialState(dryRun) {
14
+ return initialState(dryRun, false);
15
+ }
16
+ function _initialStateFirstRun(dryRun) {
17
+ return initialState(dryRun, true);
18
+ }
19
+ function _reducer(state, action) {
20
+ return reducer(state, action);
21
+ }
22
+ function initialState(dryRun, firstRun) {
23
+ return {
24
+ phase: firstRun ? "greet" : "detect",
25
+ startedAt: Date.now(),
26
+ dryRun,
27
+ engaged: false,
28
+ firstRun,
29
+ projects: [],
30
+ hookInfo: null,
31
+ hookInstalled: false,
32
+ hookVerifyMs: null,
33
+ hookError: null,
34
+ scanRan: false,
35
+ scanResult: null,
36
+ scanProgress: null,
37
+ skipped: new Set(),
38
+ };
39
+ }
40
+ function reducer(state, action) {
41
+ if (process.env.DG_DEBUG_WIZARD && action.type !== "scan_progress") {
42
+ process.stderr.write(`[wizard] action=${action.type} phase=${state.phase}\n`);
43
+ }
44
+ switch (action.type) {
45
+ // ── First-run guided tour transitions ──────────────────────────────────
46
+ case "welcome_yes":
47
+ return { ...state, phase: "value_prop", engaged: true };
48
+ case "welcome_no": {
49
+ // Engaged=true so sentinel gets marked — user won't be re-prompted.
50
+ const skipped = new Set([
51
+ "value_prop", "demo_scanning", "demo_result",
52
+ "scenario", "detect", "confirm_hook", "install_hook", "verify_hook",
53
+ "run_scan", "result_first_scan", "what_happens_next",
54
+ "pitch_app",
55
+ ]);
56
+ return { ...state, phase: "done", engaged: true, skipped };
57
+ }
58
+ case "value_prop_continue":
59
+ return { ...state, phase: "demo_scanning" };
60
+ case "demo_complete":
61
+ return { ...state, phase: "demo_result" };
62
+ case "demo_acked":
63
+ return { ...state, phase: "detect" };
64
+ case "scenario_acked":
65
+ return { ...state, phase: "confirm_hook" };
66
+ case "first_scan_acked":
67
+ // Skip what_happens_next if the user declined the hook — it talks about
68
+ // commit behavior that doesn't apply without a pre-commit hook.
69
+ if (state.firstRun && state.skipped.has("install_hook")) {
70
+ return { ...state, phase: "pitch_app" };
71
+ }
72
+ return { ...state, phase: state.firstRun ? "what_happens_next" : "pitch_app" };
73
+ case "what_happens_next_acked":
74
+ return { ...state, phase: "pitch_app" };
75
+ case "pitch_acked":
76
+ return { ...state, phase: "done" };
77
+ // ── Existing Phase 1b transitions (unchanged) ──────────────────────────
78
+ case "detected":
79
+ return {
80
+ ...state,
81
+ phase: state.firstRun ? "scenario" : "confirm_hook",
82
+ projects: action.projects,
83
+ hookInfo: action.hookInfo,
84
+ };
85
+ case "confirm_hook_yes":
86
+ if (state.hookInfo?.alreadyInstalled) {
87
+ if (state.firstRun) {
88
+ return { ...state, phase: "confirm_scan", hookInstalled: true };
89
+ }
90
+ return { ...state, phase: "verify_hook", hookInstalled: true };
91
+ }
92
+ return { ...state, phase: "install_hook" };
93
+ case "confirm_hook_no": {
94
+ const skipped = new Set(state.skipped);
95
+ skipped.add("install_hook");
96
+ if (state.firstRun) {
97
+ return { ...state, phase: "confirm_scan", skipped };
98
+ }
99
+ return { ...state, phase: "confirm_wrap", skipped };
100
+ }
101
+ case "hook_installed":
102
+ return { ...state, phase: "verify_hook", hookInstalled: true };
103
+ case "hook_install_failed":
104
+ return { ...state, phase: "done", hookError: action.error };
105
+ case "hook_verified":
106
+ return {
107
+ ...state,
108
+ phase: state.firstRun ? "confirm_scan" : "confirm_wrap",
109
+ hookVerifyMs: action.durationMs,
110
+ };
111
+ case "hook_verify_failed":
112
+ return {
113
+ ...state,
114
+ phase: state.firstRun ? "confirm_scan" : "confirm_wrap",
115
+ hookError: action.error,
116
+ };
117
+ case "wrap_confirmed":
118
+ return { ...state, phase: "confirm_scan" };
119
+ case "wrap_skipped": {
120
+ const skipped = new Set(state.skipped);
121
+ skipped.add("confirm_wrap");
122
+ return { ...state, phase: "confirm_scan", skipped };
123
+ }
124
+ case "scan_confirmed":
125
+ return { ...state, phase: "run_scan" };
126
+ case "scan_skipped": {
127
+ const skipped = new Set(state.skipped);
128
+ skipped.add("run_scan");
129
+ return {
130
+ ...state,
131
+ phase: state.firstRun ? "what_happens_next" : "show_app_link",
132
+ skipped,
133
+ };
134
+ }
135
+ case "scan_progress":
136
+ return { ...state, scanProgress: action.progress };
137
+ case "scan_done":
138
+ return {
139
+ ...state,
140
+ phase: state.firstRun ? "result_first_scan" : "show_app_link",
141
+ scanRan: true,
142
+ scanResult: action.outcome,
143
+ scanProgress: null,
144
+ };
145
+ case "app_link_acked":
146
+ return { ...state, phase: "done" };
147
+ case "go_back":
148
+ return goBack(state);
149
+ case "skip_to_end":
150
+ return { ...state, phase: "done" };
151
+ }
152
+ }
153
+ function goBack(state) {
154
+ const reversibleBack = {
155
+ value_prop: "greet",
156
+ demo_result: "value_prop",
157
+ confirm_wrap: "verify_hook", // legacy only
158
+ show_app_link: "confirm_scan", // legacy only
159
+ };
160
+ // Phases that route differently in firstRun vs legacy.
161
+ if (state.phase === "confirm_hook") {
162
+ return { ...state, phase: state.firstRun ? "scenario" : "detect" };
163
+ }
164
+ if (state.phase === "confirm_scan") {
165
+ return { ...state, phase: state.firstRun ? "confirm_hook" : "confirm_wrap" };
166
+ }
167
+ const prev = reversibleBack[state.phase];
168
+ if (prev === undefined)
169
+ return state;
170
+ return { ...state, phase: prev };
171
+ }
172
+ function canGoBack(state) {
173
+ const reversibleBack = {
174
+ value_prop: true,
175
+ demo_result: true,
176
+ confirm_hook: true,
177
+ confirm_wrap: true,
178
+ confirm_scan: true,
179
+ show_app_link: true,
180
+ };
181
+ return reversibleBack[state.phase] === true;
182
+ }
183
+ function useInit(opts = {}) {
184
+ const dryRun = opts.dryRun ?? false;
185
+ const firstRun = opts.firstRun ?? false;
186
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, initialState(dryRun, firstRun));
187
+ const scanFn = opts.scanCoreOverride ?? scan_core_1.scanProjectAtPath;
188
+ const runDetect = (0, react_1.useCallback)(() => {
189
+ const cwd = opts.cwd ?? process.cwd();
190
+ const projects = (0, discover_1.discoverProjects)(cwd);
191
+ const hookInfo = (0, hook_1.detectHookFramework)(cwd);
192
+ dispatch({ type: "detected", projects, hookInfo });
193
+ }, [opts.cwd]);
194
+ const runInstallHook = (0, react_1.useCallback)(() => {
195
+ if (!state.hookInfo)
196
+ return;
197
+ if (state.dryRun) {
198
+ // Dry run — pretend it worked.
199
+ dispatch({ type: "hook_installed" });
200
+ return;
201
+ }
202
+ try {
203
+ (0, hook_1.installHookForFramework)(state.hookInfo);
204
+ dispatch({ type: "hook_installed" });
205
+ }
206
+ catch (e) {
207
+ dispatch({
208
+ type: "hook_install_failed",
209
+ error: e instanceof Error ? e.message : String(e),
210
+ });
211
+ }
212
+ }, [state.hookInfo, state.dryRun]);
213
+ const runVerifyHook = (0, react_1.useCallback)(async () => {
214
+ if (state.dryRun) {
215
+ dispatch({ type: "hook_verified", durationMs: 0 });
216
+ return;
217
+ }
218
+ try {
219
+ const cwd = opts.cwd ?? process.cwd();
220
+ const config = (0, config_1.parseConfig)([process.argv[0] ?? "node", "init"], false);
221
+ const outcome = await scanFn(cwd, config);
222
+ if (outcome.status === "error" && outcome.error) {
223
+ dispatch({
224
+ type: "hook_verify_failed",
225
+ error: outcome.message ?? outcome.error.message,
226
+ });
227
+ return;
228
+ }
229
+ const ms = outcome.result?.durationMs ?? 0;
230
+ dispatch({ type: "hook_verified", durationMs: ms });
231
+ }
232
+ catch (e) {
233
+ dispatch({
234
+ type: "hook_verify_failed",
235
+ error: e instanceof Error ? e.message : String(e),
236
+ });
237
+ }
238
+ }, [state.dryRun, opts.cwd, scanFn]);
239
+ const runScan = (0, react_1.useCallback)(async () => {
240
+ if (state.dryRun) {
241
+ dispatch({
242
+ type: "scan_done",
243
+ outcome: {
244
+ status: "no_packages",
245
+ result: {
246
+ result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: 0 },
247
+ durationMs: 0,
248
+ scannedCount: 0,
249
+ skippedCount: 0,
250
+ },
251
+ message: "dry run — scan skipped",
252
+ },
253
+ });
254
+ return;
255
+ }
256
+ try {
257
+ const config = (0, config_1.parseConfig)([process.argv[0] ?? "node", "init", "--scan-all"], false);
258
+ const projects = state.projects.length > 0
259
+ ? state.projects
260
+ : [{ path: opts.cwd ?? process.cwd() }];
261
+ const allOutcomes = [];
262
+ for (const proj of projects) {
263
+ if (process.env.DG_DEBUG_WIZARD)
264
+ process.stderr.write(`[wizard] scanning ${proj.path}\n`);
265
+ const outcome = await scanFn(proj.path, config, (p) => {
266
+ dispatch({ type: "scan_progress", progress: p });
267
+ });
268
+ if (process.env.DG_DEBUG_WIZARD)
269
+ process.stderr.write(`[wizard] scan done ${proj.path} status=${outcome.status}\n`);
270
+ allOutcomes.push(outcome);
271
+ }
272
+ const allPackages = [];
273
+ const seen = new Set();
274
+ for (const outcome of allOutcomes) {
275
+ for (const pkg of outcome.result?.result.packages ?? []) {
276
+ const key = `${pkg.name}@${pkg.version}`;
277
+ if (seen.has(key))
278
+ continue;
279
+ seen.add(key);
280
+ allPackages.push(pkg);
281
+ }
282
+ }
283
+ const totalScanned = seen.size;
284
+ const totalDuration = allOutcomes.reduce((sum, o) => sum + (o.result?.durationMs ?? 0), 0);
285
+ const maxScore = allPackages.length > 0
286
+ ? Math.max(0, ...allPackages.map((p) => p.score))
287
+ : 0;
288
+ const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : "pass";
289
+ if (totalScanned === 0) {
290
+ dispatch({
291
+ type: "scan_done",
292
+ outcome: {
293
+ status: "no_packages",
294
+ result: {
295
+ result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: totalDuration },
296
+ durationMs: totalDuration,
297
+ scannedCount: 0,
298
+ skippedCount: 0,
299
+ },
300
+ },
301
+ });
302
+ }
303
+ else {
304
+ dispatch({
305
+ type: "scan_done",
306
+ outcome: {
307
+ status: "ok",
308
+ result: {
309
+ result: {
310
+ score: maxScore,
311
+ action,
312
+ packages: allPackages,
313
+ safeVersions: {},
314
+ durationMs: totalDuration,
315
+ },
316
+ durationMs: totalDuration,
317
+ scannedCount: totalScanned,
318
+ skippedCount: 0,
319
+ },
320
+ },
321
+ });
322
+ }
323
+ }
324
+ catch (e) {
325
+ const err = e instanceof Error ? e : new Error(String(e));
326
+ dispatch({
327
+ type: "scan_done",
328
+ outcome: { status: "error", result: null, error: err, message: err.message },
329
+ });
330
+ }
331
+ }, [state.dryRun, state.projects, opts.cwd, scanFn]);
332
+ return {
333
+ state,
334
+ canGoBack: canGoBack(state),
335
+ runDetect,
336
+ runInstallHook,
337
+ runVerifyHook,
338
+ runScan,
339
+ dispatch,
340
+ };
341
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useLogin = useLogin;
4
+ const react_1 = require("react");
5
+ const auth_1 = require("../../auth");
6
+ function reducer(state, action) {
7
+ switch (action.type) {
8
+ case "ALREADY_LOGGED_IN":
9
+ return { phase: "already_logged_in", apiKey: action.apiKey };
10
+ case "SESSION_READY":
11
+ return { phase: "ready", verifyUrl: action.verifyUrl };
12
+ case "BROWSER_OPENED":
13
+ if (state.phase !== "ready")
14
+ return state;
15
+ return { phase: "waiting", verifyUrl: state.verifyUrl };
16
+ case "AUTH_COMPLETE":
17
+ return { phase: "success", email: action.email };
18
+ case "AUTH_EXPIRED":
19
+ return { phase: "expired" };
20
+ case "ERROR":
21
+ return { phase: "error", message: action.message };
22
+ }
23
+ }
24
+ const POLL_INTERVAL_MS = 2000;
25
+ const MAX_POLL_ATTEMPTS = 150; // 5 minutes
26
+ function useLogin() {
27
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, { phase: "creating" });
28
+ const started = (0, react_1.useRef)(false);
29
+ const sessionRef = (0, react_1.useRef)(null);
30
+ const cancelledRef = (0, react_1.useRef)(false);
31
+ (0, react_1.useEffect)(() => {
32
+ if (started.current)
33
+ return;
34
+ started.current = true;
35
+ (async () => {
36
+ try {
37
+ const existing = (0, auth_1.getStoredApiKey)();
38
+ if (existing) {
39
+ // Verify the key is still valid on the server
40
+ const apiUrl = process.env.DG_API_URL || "https://api.westbayberry.com";
41
+ try {
42
+ const resp = await fetch(`${apiUrl}/v1/auth/status`, {
43
+ headers: { Authorization: `Bearer ${existing}` },
44
+ signal: AbortSignal.timeout(5000),
45
+ });
46
+ if (resp.ok) {
47
+ dispatch({ type: "ALREADY_LOGGED_IN", apiKey: existing });
48
+ return;
49
+ }
50
+ }
51
+ catch { }
52
+ // Key is invalid — clear it and proceed with fresh login
53
+ (0, auth_1.saveCredentials)("");
54
+ }
55
+ let session;
56
+ try {
57
+ session = await (0, auth_1.createAuthSession)();
58
+ }
59
+ catch (err) {
60
+ dispatch({
61
+ type: "ERROR",
62
+ message: err instanceof Error ? err.message : "Failed to create login session",
63
+ });
64
+ return;
65
+ }
66
+ sessionRef.current = session;
67
+ dispatch({ type: "SESSION_READY", verifyUrl: session.verifyUrl });
68
+ }
69
+ catch (err) {
70
+ dispatch({
71
+ type: "ERROR",
72
+ message: err instanceof Error ? err.message : String(err),
73
+ });
74
+ }
75
+ })();
76
+ return () => {
77
+ cancelledRef.current = true;
78
+ };
79
+ }, []);
80
+ const openAndPoll = (0, react_1.useCallback)(() => {
81
+ const session = sessionRef.current;
82
+ if (!session)
83
+ return;
84
+ dispatch({ type: "BROWSER_OPENED" });
85
+ (0, auth_1.openBrowser)(session.verifyUrl);
86
+ (async () => {
87
+ try {
88
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
89
+ if (cancelledRef.current)
90
+ return;
91
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
92
+ if (cancelledRef.current)
93
+ return;
94
+ try {
95
+ const result = await (0, auth_1.pollAuthSession)(session.sessionId);
96
+ if (result.status === "complete" && result.apiKey) {
97
+ (0, auth_1.saveCredentials)(result.apiKey);
98
+ dispatch({ type: "AUTH_COMPLETE", email: result.email ?? "unknown" });
99
+ return;
100
+ }
101
+ if (result.status === "expired") {
102
+ dispatch({ type: "AUTH_EXPIRED" });
103
+ return;
104
+ }
105
+ }
106
+ catch {
107
+ // Transient network error — continue polling
108
+ }
109
+ }
110
+ dispatch({ type: "AUTH_EXPIRED" });
111
+ }
112
+ catch (err) {
113
+ dispatch({
114
+ type: "ERROR",
115
+ message: err instanceof Error ? err.message : String(err),
116
+ });
117
+ }
118
+ })();
119
+ }, []);
120
+ return { state, openAndPoll };
121
+ }
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.useNpmWrapper = useNpmWrapper;
37
+ const react_1 = require("react");
38
+ const npm_wrapper_1 = require("../../npm-wrapper");
39
+ const api_1 = require("../../api");
40
+ const static_output_1 = require("../../static-output");
41
+ function reducer(_state, action) {
42
+ switch (action.type) {
43
+ case "PASSTHROUGH":
44
+ return { phase: "passthrough" };
45
+ case "RESOLVING":
46
+ return { phase: "resolving", count: action.count };
47
+ case "SCANNING":
48
+ return { phase: "scanning", count: action.count };
49
+ case "PASS":
50
+ return { phase: "pass", count: action.count };
51
+ case "WARN":
52
+ return { phase: "warn", result: action.result };
53
+ case "BLOCKED":
54
+ return { phase: "blocked", result: action.result, dgForce: action.dgForce };
55
+ case "INSTALLING":
56
+ return { phase: "installing" };
57
+ case "DONE":
58
+ return { phase: "done", exitCode: action.exitCode };
59
+ case "ERROR":
60
+ return { phase: "error", message: action.message, proceed: action.proceed };
61
+ case "TRIAL_EXHAUSTED":
62
+ return { phase: "trial_exhausted" };
63
+ }
64
+ }
65
+ function useNpmWrapper(npmArgs, config, exit) {
66
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, { phase: "resolving", count: 0 });
67
+ const started = (0, react_1.useRef)(false);
68
+ const parsedRef = (0, react_1.useRef)((0, npm_wrapper_1.parseNpmArgs)(npmArgs));
69
+ const pendingInstall = (0, react_1.useRef)(null);
70
+ const rejectRef = (0, react_1.useRef)(null);
71
+ const confirmInstall = () => {
72
+ if (pendingInstall.current) {
73
+ pendingInstall.current();
74
+ pendingInstall.current = null;
75
+ rejectRef.current = null;
76
+ }
77
+ };
78
+ const rejectInstall = () => {
79
+ if (rejectRef.current) {
80
+ rejectRef.current();
81
+ pendingInstall.current = null;
82
+ rejectRef.current = null;
83
+ }
84
+ };
85
+ (0, react_1.useEffect)(() => {
86
+ if (started.current)
87
+ return;
88
+ started.current = true;
89
+ const parsed = parsedRef.current;
90
+ (async () => {
91
+ try {
92
+ // Non-install commands: passthrough
93
+ if (!parsed.shouldScan) {
94
+ dispatch({ type: "PASSTHROUGH" });
95
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
96
+ dispatch({ type: "DONE", exitCode: code });
97
+ return;
98
+ }
99
+ // Determine packages to resolve
100
+ let specs;
101
+ if (parsed.packages.length === 0) {
102
+ // Bare `npm install` -- read from package.json
103
+ specs = (0, npm_wrapper_1.readBareInstallPackages)(process.cwd());
104
+ if (specs.length === 0) {
105
+ // Nothing to scan, just run npm
106
+ dispatch({ type: "PASSTHROUGH" });
107
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
108
+ dispatch({ type: "DONE", exitCode: code });
109
+ return;
110
+ }
111
+ }
112
+ else {
113
+ specs = parsed.packages;
114
+ }
115
+ dispatch({ type: "RESOLVING", count: specs.length });
116
+ // Resolve versions
117
+ const { resolved } = await (0, npm_wrapper_1.resolvePackages)(specs);
118
+ if (resolved.length === 0) {
119
+ // Could not resolve any packages -- pass through
120
+ dispatch({ type: "PASSTHROUGH" });
121
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
122
+ dispatch({ type: "DONE", exitCode: code });
123
+ return;
124
+ }
125
+ dispatch({ type: "SCANNING", count: resolved.length });
126
+ // Call API
127
+ const result = await (0, api_1.callAnalyzeAPI)(resolved, config);
128
+ // Route based on action
129
+ if (result.action === "pass") {
130
+ if (exit)
131
+ exit(); // unmount Ink so output persists
132
+ const chalk = (await Promise.resolve().then(() => __importStar(require("chalk")))).default;
133
+ process.stderr.write(chalk.green(` \u2713 ${resolved.length} package${resolved.length !== 1 ? "s" : ""} scanned \u2014 all clear\n`));
134
+ process.stderr.write("\n");
135
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
136
+ process.exit(code);
137
+ return;
138
+ }
139
+ if (result.action === "warn") {
140
+ if (exit)
141
+ exit(); // unmount Ink so output persists
142
+ process.stdout.write((0, static_output_1.renderResultStatic)(result, config) + "\n");
143
+ const chalk = (await Promise.resolve().then(() => __importStar(require("chalk")))).default;
144
+ process.stderr.write(chalk.yellow(" Warnings detected. Proceeding with install.\n\n"));
145
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
146
+ process.exit(code);
147
+ return;
148
+ }
149
+ if (result.action === "block") {
150
+ // Render findings
151
+ process.stdout.write((0, static_output_1.renderResultStatic)(result, config) + "\n");
152
+ if (parsed.dgForce) {
153
+ // --dg-force: bypass block
154
+ dispatch({ type: "BLOCKED", result, dgForce: true });
155
+ dispatch({ type: "INSTALLING" });
156
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
157
+ dispatch({ type: "DONE", exitCode: code });
158
+ return;
159
+ }
160
+ // Prompt user
161
+ dispatch({ type: "BLOCKED", result, dgForce: false });
162
+ // Wait for user confirmation
163
+ const shouldProceed = await new Promise((resolve) => {
164
+ pendingInstall.current = () => resolve(true);
165
+ rejectRef.current = () => resolve(false);
166
+ });
167
+ if (shouldProceed) {
168
+ dispatch({ type: "INSTALLING" });
169
+ const code = await (0, npm_wrapper_1.runNpm)(parsed.rawArgs);
170
+ dispatch({ type: "DONE", exitCode: code });
171
+ }
172
+ else {
173
+ dispatch({ type: "DONE", exitCode: 2 });
174
+ }
175
+ return;
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (error instanceof api_1.TrialExhaustedError) {
180
+ dispatch({ type: "TRIAL_EXHAUSTED" });
181
+ return;
182
+ }
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ dispatch({ type: "ERROR", message, proceed: true });
185
+ dispatch({ type: "INSTALLING" });
186
+ const code = await (0, npm_wrapper_1.runNpm)(parsedRef.current.rawArgs);
187
+ dispatch({ type: "DONE", exitCode: code });
188
+ }
189
+ })();
190
+ }, [npmArgs, config]);
191
+ return { state, confirmInstall, rejectInstall };
192
+ }