@westbayberry/dg 1.0.53 → 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.
- package/README.md +5 -1
- package/dist/index.mjs +249 -114
- package/dist/packages/cli/src/alt-screen.js +36 -0
- package/dist/packages/cli/src/api.js +322 -0
- package/dist/packages/cli/src/auth.js +218 -0
- package/dist/packages/cli/src/bin.js +386 -0
- package/dist/packages/cli/src/config.js +228 -0
- package/dist/packages/cli/src/discover.js +126 -0
- package/dist/packages/cli/src/first-run.js +135 -0
- package/dist/packages/cli/src/hook.js +360 -0
- package/dist/packages/cli/src/lockfile.js +303 -0
- package/dist/packages/cli/src/npm-wrapper.js +218 -0
- package/dist/packages/cli/src/pip-wrapper.js +273 -0
- package/dist/packages/cli/src/sanitize.js +38 -0
- package/dist/packages/cli/src/scan-core.js +144 -0
- package/dist/packages/cli/src/setup-status.js +46 -0
- package/dist/packages/cli/src/static-output.js +625 -0
- package/dist/packages/cli/src/telemetry.js +141 -0
- package/dist/packages/cli/src/ui/App.js +137 -0
- package/dist/packages/cli/src/ui/InitApp.js +391 -0
- package/dist/packages/cli/src/ui/LoginApp.js +51 -0
- package/dist/packages/cli/src/ui/NpmWrapperApp.js +73 -0
- package/dist/packages/cli/src/ui/PipWrapperApp.js +72 -0
- package/dist/packages/cli/src/ui/components/ConfirmPrompt.js +24 -0
- package/dist/packages/cli/src/ui/components/DemoScanAnimation.js +26 -0
- package/dist/packages/cli/src/ui/components/DurationLine.js +7 -0
- package/dist/packages/cli/src/ui/components/ErrorView.js +30 -0
- package/dist/packages/cli/src/ui/components/FileSavePrompt.js +210 -0
- package/dist/packages/cli/src/ui/components/InteractiveResultsView.js +557 -0
- package/dist/packages/cli/src/ui/components/Mascot.js +33 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.js +51 -0
- package/dist/packages/cli/src/ui/components/ProgressDots.js +35 -0
- package/dist/packages/cli/src/ui/components/ProjectSelector.js +60 -0
- package/dist/packages/cli/src/ui/components/ResultsView.js +105 -0
- package/dist/packages/cli/src/ui/components/ScanResultCard.js +54 -0
- package/dist/packages/cli/src/ui/components/ScoreHeader.js +142 -0
- package/dist/packages/cli/src/ui/components/SetupBanner.js +17 -0
- package/dist/packages/cli/src/ui/components/Spinner.js +11 -0
- package/dist/packages/cli/src/ui/hooks/useExpandAnimation.js +44 -0
- package/dist/packages/cli/src/ui/hooks/useInit.js +341 -0
- package/dist/packages/cli/src/ui/hooks/useLogin.js +121 -0
- package/dist/packages/cli/src/ui/hooks/useNpmWrapper.js +192 -0
- package/dist/packages/cli/src/ui/hooks/usePipWrapper.js +195 -0
- package/dist/packages/cli/src/ui/hooks/useScan.js +202 -0
- package/dist/packages/cli/src/ui/hooks/useTerminalSize.js +29 -0
- package/dist/packages/cli/src/update-check.js +152 -0
- package/dist/packages/cli/src/wizard-demo-data.js +63 -0
- package/dist/src/ecosystem.js +2 -0
- package/dist/src/lockfile/diff.js +38 -0
- package/dist/src/lockfile/parse_package_json.js +41 -0
- package/dist/src/lockfile/parse_package_lock.js +55 -0
- package/dist/src/lockfile/parse_pipfile_lock.js +69 -0
- package/dist/src/lockfile/parse_pnpm_lock.js +62 -0
- package/dist/src/lockfile/parse_poetry_lock.js +71 -0
- package/dist/src/lockfile/parse_requirements.js +83 -0
- package/dist/src/lockfile/parse_yarn_lock.js +66 -0
- package/dist/src/logger.js +21 -0
- package/dist/src/npm/h2pool.js +161 -0
- package/dist/src/npm/registry.js +299 -0
- package/dist/src/npm/tarball.js +274 -0
- package/dist/src/pypi/registry.js +299 -0
- package/dist/src/pypi/tarball.js +361 -0
- package/dist/src/types.js +2 -0
- 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
|
+
}
|