@westbayberry/dg 1.0.3 → 1.0.4

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 (3) hide show
  1. package/dist/index.mjs +4704 -4296
  2. package/package.json +1 -1
  3. package/dist/index.js +0 -1246
package/dist/index.js DELETED
@@ -1,1246 +0,0 @@
1
- #!/usr/bin/env node
2
- /******/ (() => { // webpackBootstrap
3
- /******/ "use strict";
4
- /******/ var __webpack_modules__ = ({
5
-
6
- /***/ 879:
7
- /***/ ((__unused_webpack_module, exports) => {
8
-
9
-
10
- Object.defineProperty(exports, "__esModule", ({ value: true }));
11
- exports.APIError = void 0;
12
- exports.callAnalyzeAPI = callAnalyzeAPI;
13
- class APIError extends Error {
14
- constructor(message, statusCode, body) {
15
- super(message);
16
- this.statusCode = statusCode;
17
- this.body = body;
18
- this.name = "APIError";
19
- }
20
- }
21
- exports.APIError = APIError;
22
- const BATCH_SIZE = 15;
23
- const MAX_RETRIES = 2;
24
- const RETRY_DELAY_MS = 5000;
25
- async function callAnalyzeAPI(packages, config, onProgress) {
26
- if (packages.length <= BATCH_SIZE) {
27
- return callAnalyzeBatch(packages, config);
28
- }
29
- const batches = [];
30
- for (let i = 0; i < packages.length; i += BATCH_SIZE) {
31
- batches.push(packages.slice(i, i + BATCH_SIZE));
32
- }
33
- // Process batches sequentially — parallel batches overload the server on cold scans
34
- const results = [];
35
- let completed = 0;
36
- for (const batch of batches) {
37
- const result = await callBatchWithRetry(batch, config);
38
- completed += batch.length;
39
- if (onProgress) {
40
- onProgress(completed, packages.length);
41
- }
42
- results.push(result);
43
- }
44
- return mergeResponses(results, config);
45
- }
46
- /**
47
- * Retry a batch on 504 (gateway timeout). The server continues processing
48
- * during a timeout, so retries find previously-analyzed packages cached
49
- * in Redis — fewer cold packages means faster response.
50
- */
51
- async function callBatchWithRetry(packages, config) {
52
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
53
- try {
54
- return await callAnalyzeBatch(packages, config);
55
- }
56
- catch (error) {
57
- const is504 = error instanceof APIError && error.statusCode === 504;
58
- const isTimeout = error instanceof APIError && error.statusCode === 408;
59
- if ((is504 || isTimeout) && attempt < MAX_RETRIES) {
60
- // Server is still working — wait, then retry.
61
- // Packages that finished before the timeout are now cached.
62
- const delay = RETRY_DELAY_MS * (attempt + 1);
63
- process.stderr.write(` Batch timed out, retrying in ${delay / 1000}s (attempt ${attempt + 2}/${MAX_RETRIES + 1})...\n`);
64
- await new Promise((r) => setTimeout(r, delay));
65
- continue;
66
- }
67
- throw error;
68
- }
69
- }
70
- // Unreachable, but TypeScript needs it
71
- throw new Error("Exhausted retries");
72
- }
73
- function mergeResponses(results, config) {
74
- const allPackages = results.flatMap((r) => r.packages);
75
- const maxScore = Math.max(0, ...allPackages.map((p) => p.score));
76
- const action = maxScore >= config.blockThreshold
77
- ? "block"
78
- : maxScore >= config.warnThreshold
79
- ? "warn"
80
- : "pass";
81
- const safeVersions = {};
82
- for (const r of results) {
83
- Object.assign(safeVersions, r.safeVersions);
84
- }
85
- return {
86
- score: maxScore,
87
- action: action,
88
- packages: allPackages,
89
- safeVersions,
90
- durationMs: Math.max(0, ...results.map((r) => r.durationMs)),
91
- };
92
- }
93
- async function callAnalyzeBatch(packages, config) {
94
- const url = `${config.apiUrl}/v1/analyze`;
95
- const payload = {
96
- packages: packages.map((p) => ({
97
- name: p.name,
98
- version: p.version,
99
- previousVersion: p.previousVersion,
100
- isNew: p.isNew,
101
- })),
102
- config: {
103
- blockThreshold: config.blockThreshold,
104
- warnThreshold: config.warnThreshold,
105
- },
106
- };
107
- const controller = new AbortController();
108
- const timeoutId = setTimeout(() => controller.abort(), 120000);
109
- let response;
110
- try {
111
- response = await fetch(url, {
112
- method: "POST",
113
- headers: {
114
- "Content-Type": "application/json",
115
- Authorization: `Bearer ${config.apiKey}`,
116
- "User-Agent": "dependency-guardian-cli/1.0.0",
117
- },
118
- body: JSON.stringify(payload),
119
- signal: controller.signal,
120
- });
121
- }
122
- catch (error) {
123
- clearTimeout(timeoutId);
124
- if (error instanceof Error && error.name === "AbortError") {
125
- throw new APIError("Request timed out after 120s. Try scanning fewer packages.", 408, "");
126
- }
127
- // Node.js fetch wraps the real error in .cause
128
- const cause = error instanceof Error && error.cause;
129
- const detail = cause ? `: ${cause.message || cause}` : "";
130
- throw new Error(`fetch failed${detail}`);
131
- }
132
- clearTimeout(timeoutId);
133
- if (response.status === 401) {
134
- throw new APIError("Invalid API key. Check your --api-key or DG_API_KEY value.\n" +
135
- "Get your key at https://westbayberry.com/dashboard", 401, "");
136
- }
137
- if (response.status === 429) {
138
- throw new APIError("Rate limit exceeded. Upgrade your plan at https://westbayberry.com/pricing", 429, "");
139
- }
140
- if (!response.ok) {
141
- const body = await response.text();
142
- throw new APIError(`API returned ${response.status}: ${body}`, response.status, body);
143
- }
144
- return (await response.json());
145
- }
146
-
147
-
148
- /***/ }),
149
-
150
- /***/ 988:
151
- /***/ ((__unused_webpack_module, exports) => {
152
-
153
-
154
- /**
155
- * Zero-dependency ANSI color helpers.
156
- * Disabled when stdout is not a TTY or NO_COLOR is set.
157
- */
158
- Object.defineProperty(exports, "__esModule", ({ value: true }));
159
- exports.cyan = exports.yellow = exports.green = exports.red = exports.dim = exports.bold = void 0;
160
- const enabled = process.stdout.isTTY === true && !process.env.NO_COLOR;
161
- const wrap = (code, reset) => enabled ? (s) => `\x1b[${code}m${s}\x1b[${reset}m` : (s) => s;
162
- exports.bold = wrap("1", "22");
163
- exports.dim = wrap("2", "22");
164
- exports.red = wrap("31", "39");
165
- exports.green = wrap("32", "39");
166
- exports.yellow = wrap("33", "39");
167
- exports.cyan = wrap("36", "39");
168
-
169
-
170
- /***/ }),
171
-
172
- /***/ 973:
173
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
174
-
175
-
176
- Object.defineProperty(exports, "__esModule", ({ value: true }));
177
- exports.USAGE = void 0;
178
- exports.parseConfig = parseConfig;
179
- exports.getVersion = getVersion;
180
- const node_util_1 = __nccwpck_require__(975);
181
- const node_fs_1 = __nccwpck_require__(24);
182
- const node_path_1 = __nccwpck_require__(760);
183
- const node_os_1 = __nccwpck_require__(161);
184
- function loadDgrc() {
185
- const candidates = [
186
- (0, node_path_1.join)(process.cwd(), ".dgrc.json"),
187
- (0, node_path_1.join)((0, node_os_1.homedir)(), ".dgrc.json"),
188
- ];
189
- for (const filepath of candidates) {
190
- if ((0, node_fs_1.existsSync)(filepath)) {
191
- try {
192
- return JSON.parse((0, node_fs_1.readFileSync)(filepath, "utf-8"));
193
- }
194
- catch {
195
- process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.\n`);
196
- }
197
- }
198
- }
199
- return {};
200
- }
201
- const USAGE = `
202
- Dependency Guardian — Supply chain security scanner
203
-
204
- Usage:
205
- dependency-guardian scan [options]
206
- dg scan [options]
207
- dg npm install <pkg> [npm-flags]
208
- dg wrap
209
-
210
- Commands:
211
- scan Scan dependencies for security risks (default)
212
- npm Wrap npm commands — scans packages before installing
213
- wrap Show instructions to alias npm to dg
214
-
215
- Options:
216
- --api-key <key> API key (or set DG_API_KEY env var)
217
- --api-url <url> API base URL (default: https://api.westbayberry.com)
218
- --mode <mode> block | warn | off (default: warn)
219
- --block-threshold <n> Score threshold for blocking (default: 70)
220
- --warn-threshold <n> Score threshold for warnings (default: 60)
221
- --max-packages <n> Max packages per scan (default: 200)
222
- --allowlist <pkgs> Comma-separated package names to skip
223
- --json Output JSON for CI parsing
224
- --scan-all Scan all packages, not just changed
225
- --base-lockfile <path> Path to base lockfile for explicit diff
226
- --debug Show diagnostic output (discovery, batches, timing)
227
- --no-config Skip loading .dgrc.json config file
228
- --help Show this help message
229
- --version Show version number
230
-
231
- Config File:
232
- Place a .dgrc.json in your project root or home directory.
233
- Precedence: CLI flags > env vars > .dgrc.json > defaults
234
-
235
- Environment Variables:
236
- DG_API_KEY API key
237
- DG_API_URL API base URL
238
- DG_MODE Mode (block/warn/off)
239
- DG_ALLOWLIST Comma-separated allowlist
240
- DG_DEBUG Enable debug output (set to 1)
241
-
242
- Exit Codes:
243
- 0 pass — No risks detected
244
- 1 warn — Risks detected (advisory)
245
- 2 block — High-risk packages detected
246
- 3 error — Internal error (API failure, config error)
247
-
248
- Examples:
249
- DG_API_KEY=dg_live_xxx dg scan
250
- dg scan --api-key dg_live_xxx --json
251
- dg scan --scan-all --mode block
252
- dg scan --base-lockfile ./main-lockfile.json
253
- dg npm install express lodash
254
- dg npm install @scope/pkg@^2.0.0
255
- dg npm install risky-pkg --dg-force
256
- `.trimStart();
257
- exports.USAGE = USAGE;
258
- function getVersion() {
259
- try {
260
- const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf-8"));
261
- return pkg.version ?? "1.0.0";
262
- }
263
- catch {
264
- return "1.0.0";
265
- }
266
- }
267
- function parseConfig(argv) {
268
- const { values, positionals } = (0, node_util_1.parseArgs)({
269
- args: argv.slice(2),
270
- options: {
271
- "api-key": { type: "string" },
272
- "api-url": { type: "string" },
273
- mode: { type: "string" },
274
- "block-threshold": { type: "string" },
275
- "warn-threshold": { type: "string" },
276
- "max-packages": { type: "string" },
277
- allowlist: { type: "string" },
278
- json: { type: "boolean", default: false },
279
- "scan-all": { type: "boolean", default: false },
280
- "base-lockfile": { type: "string" },
281
- debug: { type: "boolean", default: false },
282
- "no-config": { type: "boolean", default: false },
283
- help: { type: "boolean", default: false },
284
- version: { type: "boolean", default: false },
285
- },
286
- allowPositionals: true,
287
- strict: false,
288
- });
289
- if (values.help) {
290
- process.stdout.write(USAGE);
291
- process.exit(0);
292
- }
293
- if (values.version) {
294
- process.stdout.write(`dependency-guardian v${getVersion()}\n`);
295
- process.exit(0);
296
- }
297
- const command = positionals[0] ?? "scan";
298
- const noConfig = values["no-config"];
299
- const dgrc = noConfig ? {} : loadDgrc();
300
- if (values["api-key"]) {
301
- process.stderr.write("Warning: --api-key is deprecated (visible in process list). Use DG_API_KEY env var instead.\n");
302
- }
303
- const apiKey = values["api-key"] ??
304
- process.env.DG_API_KEY ??
305
- dgrc.apiKey ??
306
- "";
307
- if (!apiKey) {
308
- process.stderr.write("Error: API key required. Set DG_API_KEY environment variable.\n" +
309
- "Do NOT pass keys via --api-key (visible in process list).\n" +
310
- "Get your key at https://westbayberry.com/dashboard\n");
311
- process.exit(1);
312
- }
313
- const modeRaw = values.mode ??
314
- process.env.DG_MODE ??
315
- dgrc.mode ??
316
- "warn";
317
- if (!["block", "warn", "off"].includes(modeRaw)) {
318
- process.stderr.write(`Error: Invalid mode "${modeRaw}". Must be block, warn, or off.\n`);
319
- process.exit(1);
320
- }
321
- const allowlistRaw = values.allowlist ??
322
- process.env.DG_ALLOWLIST ??
323
- "";
324
- const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
325
- const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
326
- const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
327
- const debug = values.debug || process.env.DG_DEBUG === "1";
328
- if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
329
- process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
330
- process.exit(1);
331
- }
332
- if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
333
- process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
334
- process.exit(1);
335
- }
336
- if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 10000) {
337
- process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
338
- process.exit(1);
339
- }
340
- return {
341
- apiKey,
342
- apiUrl: values["api-url"] ??
343
- process.env.DG_API_URL ??
344
- dgrc.apiUrl ??
345
- "https://api.westbayberry.com",
346
- mode: modeRaw,
347
- blockThreshold,
348
- warnThreshold,
349
- maxPackages,
350
- allowlist: allowlistRaw
351
- ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean)
352
- : (dgrc.allowlist ?? []),
353
- json: values.json,
354
- scanAll: values["scan-all"],
355
- baseLockfile: values["base-lockfile"] ?? null,
356
- command,
357
- debug,
358
- };
359
- }
360
-
361
-
362
- /***/ }),
363
-
364
- /***/ 746:
365
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
366
-
367
-
368
- Object.defineProperty(exports, "__esModule", ({ value: true }));
369
- exports.discoverChanges = discoverChanges;
370
- const node_child_process_1 = __nccwpck_require__(421);
371
- const node_fs_1 = __nccwpck_require__(24);
372
- const node_path_1 = __nccwpck_require__(760);
373
- const parse_package_lock_1 = __nccwpck_require__(88);
374
- const diff_1 = __nccwpck_require__(229);
375
- const parse_package_json_1 = __nccwpck_require__(417);
376
- /**
377
- * Discover changed (or all) packages by reading the local lockfile
378
- * and comparing against a base.
379
- */
380
- function discoverChanges(cwd, config) {
381
- const lockfilePath = findLockfile(cwd);
382
- if (!lockfilePath) {
383
- throw new Error("No package-lock.json found. Run from your project root or use --base-lockfile.");
384
- }
385
- const headContent = (0, node_fs_1.readFileSync)(lockfilePath, "utf-8");
386
- const headParsed = (0, parse_package_lock_1.parseLockfile)(headContent);
387
- const directDeps = getDirectDeps(cwd);
388
- // 1. --scan-all: treat everything as new
389
- if (config.scanAll) {
390
- const packages = [];
391
- for (const [name, entry] of headParsed.packages) {
392
- if (packages.length >= config.maxPackages)
393
- break;
394
- packages.push({
395
- name,
396
- version: entry.version,
397
- previousVersion: null,
398
- isNew: true,
399
- });
400
- }
401
- return { packages, method: "scan-all", skipped: [] };
402
- }
403
- // 2. --base-lockfile: explicit diff
404
- if (config.baseLockfile) {
405
- if (!(0, node_fs_1.existsSync)(config.baseLockfile)) {
406
- throw new Error(`Base lockfile not found: ${config.baseLockfile}`);
407
- }
408
- const baseContent = (0, node_fs_1.readFileSync)(config.baseLockfile, "utf-8");
409
- const baseParsed = (0, parse_package_lock_1.parseLockfile)(baseContent);
410
- const diff = (0, diff_1.diffLockfiles)(baseParsed, headParsed, config.maxPackages, directDeps);
411
- return {
412
- packages: diff.changes.map(toPackageInput),
413
- method: "base-lockfile",
414
- skipped: diff.skipped,
415
- };
416
- }
417
- // 3. Git auto-diff
418
- const baseContent = getGitBaseLockfile(cwd);
419
- if (baseContent !== null) {
420
- const baseParsed = (0, parse_package_lock_1.parseLockfile)(baseContent);
421
- const diff = (0, diff_1.diffLockfiles)(baseParsed, headParsed, config.maxPackages, directDeps);
422
- return {
423
- packages: diff.changes.map(toPackageInput),
424
- method: "git-diff",
425
- skipped: diff.skipped,
426
- };
427
- }
428
- // 4. Fallback: try package.json diff, resolve ranges via lockfile
429
- const pkgJsonPath = (0, node_path_1.join)(cwd, "package.json");
430
- if ((0, node_fs_1.existsSync)(pkgJsonPath)) {
431
- const headPkgJson = (0, node_fs_1.readFileSync)(pkgJsonPath, "utf-8");
432
- const basePkgJson = getGitBaseFile(cwd, "package.json");
433
- if (basePkgJson !== null) {
434
- const diff = (0, parse_package_json_1.diffPackageJsons)(basePkgJson, headPkgJson, config.maxPackages);
435
- // Resolve semver ranges (e.g. "^3.0.1") to exact versions from the lockfile
436
- const resolved = diff.changes.map((change) => {
437
- const lockEntry = headParsed.packages.get(change.name);
438
- return {
439
- ...change,
440
- newVersion: lockEntry?.version ?? change.newVersion,
441
- };
442
- });
443
- return {
444
- packages: resolved.map(toPackageInput),
445
- method: "fallback",
446
- skipped: [],
447
- };
448
- }
449
- }
450
- // Ultimate fallback: scan everything
451
- const packages = [];
452
- for (const [name, entry] of headParsed.packages) {
453
- if (packages.length >= config.maxPackages)
454
- break;
455
- packages.push({
456
- name,
457
- version: entry.version,
458
- previousVersion: null,
459
- isNew: true,
460
- });
461
- }
462
- return { packages, method: "fallback", skipped: [] };
463
- }
464
- function findLockfile(cwd) {
465
- const candidates = ["package-lock.json", "npm-shrinkwrap.json"];
466
- for (const name of candidates) {
467
- const p = (0, node_path_1.join)(cwd, name);
468
- if ((0, node_fs_1.existsSync)(p))
469
- return p;
470
- }
471
- return null;
472
- }
473
- function getDirectDeps(cwd) {
474
- try {
475
- const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(cwd, "package.json"), "utf-8");
476
- const pkg = JSON.parse(content);
477
- return new Set([
478
- ...Object.keys(pkg.dependencies ?? {}),
479
- ...Object.keys(pkg.devDependencies ?? {}),
480
- ]);
481
- }
482
- catch {
483
- return new Set();
484
- }
485
- }
486
- function getGitBaseLockfile(cwd) {
487
- try {
488
- const mergeBase = (0, node_child_process_1.execSync)("git merge-base HEAD main", {
489
- cwd,
490
- encoding: "utf-8",
491
- stdio: ["pipe", "pipe", "pipe"],
492
- }).trim();
493
- if (!mergeBase)
494
- return null;
495
- return (0, node_child_process_1.execSync)(`git show ${mergeBase}:package-lock.json`, {
496
- cwd,
497
- encoding: "utf-8",
498
- stdio: ["pipe", "pipe", "pipe"],
499
- });
500
- }
501
- catch {
502
- // Not a git repo, no main branch, or file doesn't exist at base
503
- return null;
504
- }
505
- }
506
- function getGitBaseFile(cwd, filename) {
507
- try {
508
- const mergeBase = (0, node_child_process_1.execSync)("git merge-base HEAD main", {
509
- cwd,
510
- encoding: "utf-8",
511
- stdio: ["pipe", "pipe", "pipe"],
512
- }).trim();
513
- if (!mergeBase)
514
- return null;
515
- return (0, node_child_process_1.execSync)(`git show ${mergeBase}:${filename}`, {
516
- cwd,
517
- encoding: "utf-8",
518
- stdio: ["pipe", "pipe", "pipe"],
519
- });
520
- }
521
- catch {
522
- return null;
523
- }
524
- }
525
- function toPackageInput(change) {
526
- return {
527
- name: change.name,
528
- version: change.newVersion,
529
- previousVersion: change.oldVersion,
530
- isNew: change.oldVersion === null,
531
- };
532
- }
533
-
534
-
535
- /***/ }),
536
-
537
- /***/ 124:
538
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
539
-
540
-
541
- Object.defineProperty(exports, "__esModule", ({ value: true }));
542
- exports.parseNpmArgs = parseNpmArgs;
543
- exports.parsePackageSpec = parsePackageSpec;
544
- exports.resolveVersion = resolveVersion;
545
- exports.resolvePackages = resolvePackages;
546
- exports.runNpm = runNpm;
547
- exports.handleNpmCommand = handleNpmCommand;
548
- exports.handleWrapCommand = handleWrapCommand;
549
- const node_child_process_1 = __nccwpck_require__(421);
550
- const color_1 = __nccwpck_require__(988);
551
- const api_1 = __nccwpck_require__(879);
552
- const output_1 = __nccwpck_require__(202);
553
- /** npm commands that install packages and should trigger a scan */
554
- const INSTALL_COMMANDS = new Set(["install", "i", "add", "update", "up"]);
555
- /**
556
- * Parse the argv after `dg npm ...` to extract the npm command and package specifiers.
557
- */
558
- function parseNpmArgs(args) {
559
- let dgForce = false;
560
- const filtered = [];
561
- // Extract dg-specific flags before passing to npm
562
- for (const arg of args) {
563
- if (arg === "--dg-force") {
564
- dgForce = true;
565
- }
566
- else {
567
- filtered.push(arg);
568
- }
569
- }
570
- const command = filtered[0] ?? "";
571
- const shouldScan = INSTALL_COMMANDS.has(command);
572
- // Extract package specifiers: anything that's not a flag and not the command itself
573
- const packages = [];
574
- if (shouldScan) {
575
- for (let i = 1; i < filtered.length; i++) {
576
- const arg = filtered[i];
577
- // Skip flags and their values
578
- if (arg.startsWith("-")) {
579
- // Flags that take a value: skip next arg too
580
- if (flagTakesValue(arg)) {
581
- i++;
582
- }
583
- continue;
584
- }
585
- packages.push(arg);
586
- }
587
- }
588
- return {
589
- command,
590
- packages,
591
- rawArgs: filtered,
592
- dgForce,
593
- shouldScan,
594
- };
595
- }
596
- /** npm flags that consume the next argument as a value */
597
- function flagTakesValue(flag) {
598
- const valueFlagPrefixes = [
599
- "--save-prefix",
600
- "--tag",
601
- "--registry",
602
- "--cache",
603
- "--prefix",
604
- "--fund",
605
- "--omit",
606
- "--install-strategy",
607
- "--workspace",
608
- ];
609
- // Short flags that take values
610
- if (flag === "-w")
611
- return true;
612
- for (const prefix of valueFlagPrefixes) {
613
- if (flag === prefix)
614
- return true;
615
- }
616
- // --flag=value style doesn't consume next arg
617
- return false;
618
- }
619
- /**
620
- * Parse a package specifier like "express", "@scope/pkg@^2.0.0", "pkg@latest"
621
- * into { name, versionSpec }.
622
- */
623
- function parsePackageSpec(spec) {
624
- // Scoped: @scope/pkg@version
625
- if (spec.startsWith("@")) {
626
- const slashIdx = spec.indexOf("/");
627
- if (slashIdx === -1) {
628
- return { name: spec, versionSpec: null };
629
- }
630
- const afterSlash = spec.slice(slashIdx + 1);
631
- const atIdx = afterSlash.indexOf("@");
632
- if (atIdx === -1) {
633
- return { name: spec, versionSpec: null };
634
- }
635
- return {
636
- name: spec.slice(0, slashIdx + 1 + atIdx),
637
- versionSpec: afterSlash.slice(atIdx + 1),
638
- };
639
- }
640
- // Unscoped: pkg@version
641
- const atIdx = spec.indexOf("@");
642
- if (atIdx === -1 || atIdx === 0) {
643
- return { name: spec, versionSpec: null };
644
- }
645
- return {
646
- name: spec.slice(0, atIdx),
647
- versionSpec: spec.slice(atIdx + 1),
648
- };
649
- }
650
- /**
651
- * Resolve what version npm would install for a given package specifier.
652
- * Uses `npm view <spec> version` to get the resolved version.
653
- */
654
- function resolveVersion(spec) {
655
- try {
656
- const version = (0, node_child_process_1.execSync)(`npm view "${spec}" version`, {
657
- encoding: "utf-8",
658
- stdio: ["pipe", "pipe", "pipe"],
659
- timeout: 15000,
660
- }).trim();
661
- return version || null;
662
- }
663
- catch {
664
- return null;
665
- }
666
- }
667
- /**
668
- * Build PackageInput[] from package specifiers by resolving versions.
669
- */
670
- function resolvePackages(specs) {
671
- const resolved = [];
672
- const failed = [];
673
- for (const spec of specs) {
674
- const { name, versionSpec } = parsePackageSpec(spec);
675
- const querySpec = versionSpec ? `${name}@${versionSpec}` : name;
676
- const version = resolveVersion(querySpec);
677
- if (version) {
678
- resolved.push({
679
- name,
680
- version,
681
- previousVersion: null,
682
- isNew: true,
683
- });
684
- }
685
- else {
686
- failed.push(spec);
687
- }
688
- }
689
- return { resolved, failed };
690
- }
691
- /**
692
- * Run the actual npm command, inheriting stdio.
693
- * Returns the npm exit code.
694
- */
695
- function runNpm(args) {
696
- return new Promise((resolve) => {
697
- const child = (0, node_child_process_1.spawn)("npm", args, {
698
- stdio: "inherit",
699
- shell: false,
700
- });
701
- child.on("close", (code) => resolve(code ?? 1));
702
- child.on("error", () => resolve(1));
703
- });
704
- }
705
- /**
706
- * Main npm wrapper entry point.
707
- */
708
- async function handleNpmCommand(npmArgs, config) {
709
- const parsed = parseNpmArgs(npmArgs);
710
- // Non-install commands: pass through directly
711
- if (!parsed.shouldScan) {
712
- const code = await runNpm(parsed.rawArgs);
713
- process.exit(code);
714
- }
715
- // No packages specified (e.g. bare `npm install` from package.json)
716
- if (parsed.packages.length === 0) {
717
- process.stderr.write((0, color_1.dim)(" Dependency Guardian: no new packages specified, passing through to npm.\n"));
718
- const code = await runNpm(parsed.rawArgs);
719
- process.exit(code);
720
- }
721
- // Resolve versions
722
- process.stderr.write((0, color_1.dim)(` Resolving ${parsed.packages.length} package${parsed.packages.length !== 1 ? "s" : ""}...\n`));
723
- const { resolved, failed } = resolvePackages(parsed.packages);
724
- if (failed.length > 0) {
725
- process.stderr.write((0, color_1.yellow)(` Warning: Could not resolve versions for: ${failed.join(", ")}\n`));
726
- }
727
- if (resolved.length === 0) {
728
- process.stderr.write((0, color_1.dim)(" No packages to scan. Passing through to npm.\n"));
729
- const code = await runNpm(parsed.rawArgs);
730
- process.exit(code);
731
- }
732
- // Filter allowlist
733
- const toScan = resolved.filter((p) => !config.allowlist.includes(p.name));
734
- if (toScan.length === 0) {
735
- process.stderr.write((0, color_1.dim)(" All packages are allowlisted. Passing through to npm.\n"));
736
- const code = await runNpm(parsed.rawArgs);
737
- process.exit(code);
738
- }
739
- // Scan
740
- process.stderr.write((0, color_1.dim)(` Scanning ${toScan.length} package${toScan.length !== 1 ? "s" : ""}...\n`));
741
- let result;
742
- try {
743
- const startMs = Date.now();
744
- result = await (0, api_1.callAnalyzeAPI)(toScan, config);
745
- const elapsed = Date.now() - startMs;
746
- if (config.debug) {
747
- process.stderr.write((0, color_1.dim)(` [debug] API responded in ${elapsed}ms, action=${result.action}, score=${result.score}\n`));
748
- }
749
- }
750
- catch (error) {
751
- // API unavailable — warn and proceed
752
- const msg = error instanceof Error ? error.message : String(error);
753
- process.stderr.write((0, color_1.yellow)(` Warning: Scan failed (${msg}). Proceeding with install.\n`));
754
- const code = await runNpm(parsed.rawArgs);
755
- process.exit(code);
756
- return; // unreachable, but helps TypeScript
757
- }
758
- // Handle result
759
- if (result.action === "pass") {
760
- process.stderr.write((0, color_1.green)(` ${(0, color_1.bold)("\u2713")} ${toScan.length} package${toScan.length !== 1 ? "s" : ""} scanned \u2014 all clear\n\n`));
761
- const code = await runNpm(parsed.rawArgs);
762
- process.exit(code);
763
- }
764
- // Render findings
765
- const output = (0, output_1.renderResult)(result, config);
766
- process.stdout.write(output + "\n");
767
- if (result.action === "warn") {
768
- process.stderr.write((0, color_1.yellow)(" Warnings detected. Proceeding with install.\n\n"));
769
- const code = await runNpm(parsed.rawArgs);
770
- process.exit(code);
771
- }
772
- // Block
773
- if (result.action === "block") {
774
- if (parsed.dgForce) {
775
- process.stderr.write((0, color_1.yellow)((0, color_1.bold)(" --dg-force: Bypassing block. Install at your own risk.\n\n")));
776
- const code = await runNpm(parsed.rawArgs);
777
- process.exit(code);
778
- }
779
- process.stderr.write((0, color_1.red)((0, color_1.bold)(" BLOCKED: ")) +
780
- (0, color_1.red)("High-risk packages detected. Install aborted.\n"));
781
- process.stderr.write((0, color_1.dim)(" Use --dg-force to bypass this check.\n\n"));
782
- process.exit(2);
783
- }
784
- }
785
- const WRAP_USAGE = `
786
- Set up dg as your npm wrapper:
787
-
788
- Option 1 — Shell alias (recommended):
789
- Add to your ~/.zshrc or ~/.bashrc:
790
- alias npm='dg npm'
791
-
792
- Option 2 — Per-project .npmrc:
793
- Not yet supported.
794
-
795
- Once set up, every \`npm install\` will be scanned automatically.
796
- `.trimStart();
797
- function handleWrapCommand() {
798
- process.stdout.write(WRAP_USAGE);
799
- }
800
-
801
-
802
- /***/ }),
803
-
804
- /***/ 202:
805
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
806
-
807
-
808
- Object.defineProperty(exports, "__esModule", ({ value: true }));
809
- exports.renderResult = renderResult;
810
- const color_1 = __nccwpck_require__(988);
811
- const SEVERITY_LABELS = {
812
- 5: "CRITICAL",
813
- 4: "HIGH",
814
- 3: "MEDIUM",
815
- 2: "LOW",
816
- 1: "INFO",
817
- };
818
- function severityColor(sev) {
819
- if (sev >= 5)
820
- return (s) => (0, color_1.bold)((0, color_1.red)(s));
821
- if (sev >= 4)
822
- return color_1.red;
823
- if (sev >= 3)
824
- return color_1.yellow;
825
- if (sev >= 2)
826
- return color_1.cyan;
827
- return color_1.dim;
828
- }
829
- function actionColor(action) {
830
- if (action === "block")
831
- return color_1.red;
832
- if (action === "warn")
833
- return color_1.yellow;
834
- return color_1.green;
835
- }
836
- function pad(s, len) {
837
- return s + " ".repeat(Math.max(0, len - s.length));
838
- }
839
- function rpad(s, len) {
840
- return " ".repeat(Math.max(0, len - s.length)) + s;
841
- }
842
- function renderResult(result, config) {
843
- if (config.json) {
844
- return JSON.stringify(result, null, 2);
845
- }
846
- const lines = [];
847
- const actionStr = result.action.toUpperCase();
848
- const colorAction = actionColor(result.action);
849
- // Header
850
- lines.push("");
851
- lines.push(` ${(0, color_1.bold)("Dependency Guardian")}`);
852
- lines.push(` Score: ${(0, color_1.bold)(String(result.score))}${" ".repeat(Math.max(1, 50 - String(result.score).length))}${colorAction(actionStr)}`);
853
- lines.push("");
854
- // Summary
855
- const flagged = result.packages.filter((p) => p.score > 0).length;
856
- const total = result.packages.length;
857
- lines.push(` ${total} package${total !== 1 ? "s" : ""} scanned, ${flagged} flagged`);
858
- lines.push("");
859
- if (total === 0) {
860
- lines.push(" No packages to scan.");
861
- lines.push("");
862
- return lines.join("\n");
863
- }
864
- // Package table — adapt to terminal width
865
- const termWidth = process.stdout.columns || 80;
866
- const COL_NAME = Math.max(16, Math.min(40, Math.floor(termWidth * 0.3)));
867
- const COL_VER = Math.max(14, Math.min(30, Math.floor(termWidth * 0.25)));
868
- const COL_SCORE = 7;
869
- lines.push(` ${(0, color_1.dim)(pad("Package", COL_NAME))}${(0, color_1.dim)(pad("Version", COL_VER))}${(0, color_1.dim)(pad("Score", COL_SCORE))}${(0, color_1.dim)("Action")}`);
870
- lines.push(` ${(0, color_1.dim)(pad("\u2500".repeat(COL_NAME - 2), COL_NAME))}${(0, color_1.dim)(pad("\u2500".repeat(COL_VER - 2), COL_VER))}${(0, color_1.dim)(pad("\u2500".repeat(COL_SCORE - 2), COL_SCORE))}${(0, color_1.dim)("\u2500".repeat(6))}`);
871
- // Sort: flagged first, then by score desc
872
- const sorted = [...result.packages].sort((a, b) => b.score - a.score);
873
- for (const pkg of sorted) {
874
- const versionStr = pkg.version;
875
- const safe = result.safeVersions[pkg.name];
876
- const verDisplay = safe ? `${(0, color_1.dim)(safe + " \u2192 ")}${versionStr}` : versionStr;
877
- const pkgAction = pkg.score >= config.blockThreshold
878
- ? "BLOCK"
879
- : pkg.score >= config.warnThreshold
880
- ? "WARN"
881
- : "pass";
882
- const pkgColor = actionColor(pkg.score >= config.blockThreshold
883
- ? "block"
884
- : pkg.score >= config.warnThreshold
885
- ? "warn"
886
- : "pass");
887
- lines.push(` ${pad(truncate(pkg.name, COL_NAME - 2), COL_NAME)}${pad(verDisplay, COL_VER)}${rpad(String(pkg.score), COL_SCORE - 2)} ${pkgColor(pkgAction)}`);
888
- }
889
- lines.push("");
890
- // Detailed findings for flagged packages
891
- const flaggedPkgs = sorted.filter((p) => p.score > 0);
892
- for (const pkg of flaggedPkgs) {
893
- lines.push(renderPackageDetail(pkg, result.safeVersions[pkg.name]));
894
- }
895
- // Duration
896
- if (result.durationMs) {
897
- lines.push(` ${(0, color_1.dim)(`Completed in ${(result.durationMs / 1000).toFixed(1)}s`)}`);
898
- lines.push("");
899
- }
900
- return lines.join("\n");
901
- }
902
- function renderPackageDetail(pkg, safeVersion) {
903
- const lines = [];
904
- const header = `${pkg.name}@${pkg.version} (score: ${pkg.score})`;
905
- const rule = "\u2500".repeat(Math.max(0, 50 - header.length));
906
- lines.push(` ${(0, color_1.dim)("\u2500\u2500")} ${(0, color_1.bold)(header)} ${(0, color_1.dim)(rule)}`);
907
- lines.push("");
908
- for (const finding of pkg.findings) {
909
- // Skip low-severity informational findings in detail view
910
- if (finding.severity <= 1 && !finding.critical)
911
- continue;
912
- const sevLabel = SEVERITY_LABELS[finding.severity] ?? "INFO";
913
- const colorFn = severityColor(finding.severity);
914
- lines.push(` ${colorFn(pad(sevLabel, 10))}${finding.id} \u2014 ${finding.title} (sev ${finding.severity}, conf ${finding.confidence.toFixed(2)})`);
915
- // Show up to 3 evidence lines
916
- const evidenceLimit = 3;
917
- for (let i = 0; i < Math.min(finding.evidence.length, evidenceLimit); i++) {
918
- lines.push(` ${" ".repeat(10)}${(0, color_1.dim)(truncate(finding.evidence[i], 80))}`);
919
- }
920
- if (finding.evidence.length > evidenceLimit) {
921
- lines.push(` ${" ".repeat(10)}${(0, color_1.dim)(`... and ${finding.evidence.length - evidenceLimit} more`)}`);
922
- }
923
- lines.push("");
924
- }
925
- if (safeVersion) {
926
- lines.push(` ${(0, color_1.green)(`Safe version: ${pkg.name}@${safeVersion}`)}`);
927
- lines.push("");
928
- }
929
- return lines.join("\n");
930
- }
931
- function truncate(s, max) {
932
- if (s.length <= max)
933
- return s;
934
- return s.slice(0, max - 1) + "\u2026";
935
- }
936
-
937
-
938
- /***/ }),
939
-
940
- /***/ 229:
941
- /***/ ((__unused_webpack_module, exports) => {
942
-
943
-
944
- Object.defineProperty(exports, "__esModule", ({ value: true }));
945
- exports.diffLockfiles = diffLockfiles;
946
- function diffLockfiles(base, head, maxPackages, directDeps) {
947
- const allChanges = [];
948
- for (const [name, headEntry] of head.packages) {
949
- const baseEntry = base?.packages.get(name);
950
- if (!baseEntry) {
951
- allChanges.push({
952
- name,
953
- oldVersion: null,
954
- newVersion: headEntry.version,
955
- isDirect: directDeps?.has(name) ?? false,
956
- isDev: headEntry.dev ?? false,
957
- });
958
- }
959
- else if (baseEntry.version !== headEntry.version) {
960
- allChanges.push({
961
- name,
962
- oldVersion: baseEntry.version,
963
- newVersion: headEntry.version,
964
- isDirect: directDeps?.has(name) ?? false,
965
- isDev: headEntry.dev ?? false,
966
- });
967
- }
968
- }
969
- allChanges.sort((a, b) => {
970
- if (a.isDirect !== b.isDirect)
971
- return a.isDirect ? -1 : 1;
972
- return a.name.localeCompare(b.name);
973
- });
974
- if (allChanges.length <= maxPackages) {
975
- return { changes: allChanges, skipped: [] };
976
- }
977
- const changes = allChanges.slice(0, maxPackages);
978
- const skipped = allChanges.slice(maxPackages).map((c) => c.name);
979
- return { changes, skipped };
980
- }
981
-
982
-
983
- /***/ }),
984
-
985
- /***/ 417:
986
- /***/ ((__unused_webpack_module, exports) => {
987
-
988
-
989
- Object.defineProperty(exports, "__esModule", ({ value: true }));
990
- exports.diffPackageJsons = diffPackageJsons;
991
- function diffPackageJsons(baseContent, headContent, maxPackages) {
992
- const head = JSON.parse(headContent);
993
- const headDeps = {
994
- ...head.dependencies,
995
- ...head.devDependencies,
996
- };
997
- let baseDeps = {};
998
- if (baseContent) {
999
- const base = JSON.parse(baseContent);
1000
- baseDeps = { ...base.dependencies, ...base.devDependencies };
1001
- }
1002
- const changes = [];
1003
- for (const [name, version] of Object.entries(headDeps)) {
1004
- if (changes.length >= maxPackages)
1005
- break;
1006
- const baseVersion = baseDeps[name];
1007
- const isDev = !!(head.devDependencies && head.devDependencies[name]);
1008
- if (!baseVersion) {
1009
- changes.push({
1010
- name,
1011
- oldVersion: null,
1012
- newVersion: version,
1013
- isDirect: true,
1014
- isDev,
1015
- });
1016
- }
1017
- else if (baseVersion !== version) {
1018
- changes.push({
1019
- name,
1020
- oldVersion: baseVersion,
1021
- newVersion: version,
1022
- isDirect: true,
1023
- isDev,
1024
- });
1025
- }
1026
- }
1027
- return { changes };
1028
- }
1029
-
1030
-
1031
- /***/ }),
1032
-
1033
- /***/ 88:
1034
- /***/ ((__unused_webpack_module, exports) => {
1035
-
1036
-
1037
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1038
- exports.parseLockfile = parseLockfile;
1039
- function parseLockfile(content) {
1040
- const json = JSON.parse(content);
1041
- const lockfileVersion = json.lockfileVersion ?? 1;
1042
- const packages = new Map();
1043
- if (lockfileVersion >= 2 && json.packages) {
1044
- for (const [path, entry] of Object.entries(json.packages)) {
1045
- if (path === "")
1046
- continue;
1047
- const name = extractPackageName(path);
1048
- if (name && !packages.has(name)) {
1049
- const e = entry;
1050
- packages.set(name, {
1051
- version: e.version ?? "",
1052
- resolved: e.resolved,
1053
- integrity: e.integrity,
1054
- dev: e.dev,
1055
- });
1056
- }
1057
- }
1058
- }
1059
- else if (json.dependencies) {
1060
- parseLegacyDeps(json.dependencies, packages);
1061
- }
1062
- return { lockfileVersion, packages };
1063
- }
1064
- function extractPackageName(nodePath) {
1065
- const prefix = "node_modules/";
1066
- const lastIdx = nodePath.lastIndexOf(prefix);
1067
- if (lastIdx === -1)
1068
- return null;
1069
- const name = nodePath.slice(lastIdx + prefix.length);
1070
- return name || null;
1071
- }
1072
- function parseLegacyDeps(deps, packages, parentPrefix = "") {
1073
- for (const [name, value] of Object.entries(deps)) {
1074
- const fullName = parentPrefix ? `${parentPrefix}/${name}` : name;
1075
- const entry = value;
1076
- packages.set(fullName, {
1077
- version: entry.version ?? "",
1078
- resolved: entry.resolved,
1079
- integrity: entry.integrity,
1080
- dev: entry.dev,
1081
- });
1082
- if (entry.dependencies) {
1083
- parseLegacyDeps(entry.dependencies, packages);
1084
- }
1085
- }
1086
- }
1087
-
1088
-
1089
- /***/ }),
1090
-
1091
- /***/ 421:
1092
- /***/ ((module) => {
1093
-
1094
- module.exports = require("node:child_process");
1095
-
1096
- /***/ }),
1097
-
1098
- /***/ 24:
1099
- /***/ ((module) => {
1100
-
1101
- module.exports = require("node:fs");
1102
-
1103
- /***/ }),
1104
-
1105
- /***/ 161:
1106
- /***/ ((module) => {
1107
-
1108
- module.exports = require("node:os");
1109
-
1110
- /***/ }),
1111
-
1112
- /***/ 760:
1113
- /***/ ((module) => {
1114
-
1115
- module.exports = require("node:path");
1116
-
1117
- /***/ }),
1118
-
1119
- /***/ 975:
1120
- /***/ ((module) => {
1121
-
1122
- module.exports = require("node:util");
1123
-
1124
- /***/ })
1125
-
1126
- /******/ });
1127
- /************************************************************************/
1128
- /******/ // The module cache
1129
- /******/ var __webpack_module_cache__ = {};
1130
- /******/
1131
- /******/ // The require function
1132
- /******/ function __nccwpck_require__(moduleId) {
1133
- /******/ // Check if module is in cache
1134
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
1135
- /******/ if (cachedModule !== undefined) {
1136
- /******/ return cachedModule.exports;
1137
- /******/ }
1138
- /******/ // Create a new module (and put it into the cache)
1139
- /******/ var module = __webpack_module_cache__[moduleId] = {
1140
- /******/ // no module.id needed
1141
- /******/ // no module.loaded needed
1142
- /******/ exports: {}
1143
- /******/ };
1144
- /******/
1145
- /******/ // Execute the module function
1146
- /******/ var threw = true;
1147
- /******/ try {
1148
- /******/ __webpack_modules__[moduleId](module, module.exports, __nccwpck_require__);
1149
- /******/ threw = false;
1150
- /******/ } finally {
1151
- /******/ if(threw) delete __webpack_module_cache__[moduleId];
1152
- /******/ }
1153
- /******/
1154
- /******/ // Return the exports of the module
1155
- /******/ return module.exports;
1156
- /******/ }
1157
- /******/
1158
- /************************************************************************/
1159
- /******/ /* webpack/runtime/compat */
1160
- /******/
1161
- /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
1162
- /******/
1163
- /************************************************************************/
1164
- var __webpack_exports__ = {};
1165
- // This entry need to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
1166
- (() => {
1167
- var exports = __webpack_exports__;
1168
-
1169
- Object.defineProperty(exports, "__esModule", ({ value: true }));
1170
- const config_1 = __nccwpck_require__(973);
1171
- const lockfile_1 = __nccwpck_require__(746);
1172
- const api_1 = __nccwpck_require__(879);
1173
- const output_1 = __nccwpck_require__(202);
1174
- const color_1 = __nccwpck_require__(988);
1175
- const npm_wrapper_1 = __nccwpck_require__(124);
1176
- async function main() {
1177
- // Check for `dg npm ...` or `dg wrap` before full config parsing
1178
- const rawCommand = process.argv[2];
1179
- if (rawCommand === "npm") {
1180
- // Parse config but don't require lockfile discovery
1181
- const config = (0, config_1.parseConfig)(process.argv);
1182
- await (0, npm_wrapper_1.handleNpmCommand)(process.argv.slice(3), config);
1183
- return;
1184
- }
1185
- if (rawCommand === "wrap") {
1186
- (0, npm_wrapper_1.handleWrapCommand)();
1187
- return;
1188
- }
1189
- const config = (0, config_1.parseConfig)(process.argv);
1190
- const dbg = (msg) => {
1191
- if (config.debug)
1192
- process.stderr.write((0, color_1.dim)(` [debug] ${msg}\n`));
1193
- };
1194
- if (config.mode === "off") {
1195
- process.stderr.write((0, color_1.dim)(" Dependency Guardian: mode is off — skipping.\n"));
1196
- process.exit(0);
1197
- }
1198
- dbg(`mode=${config.mode} block=${config.blockThreshold} warn=${config.warnThreshold} max=${config.maxPackages}`);
1199
- dbg(`api=${config.apiUrl}`);
1200
- // Discover packages
1201
- process.stderr.write((0, color_1.dim)(" Discovering package changes...\n"));
1202
- const discovery = (0, lockfile_1.discoverChanges)(process.cwd(), config);
1203
- dbg(`discovery method: ${discovery.method}`);
1204
- if (discovery.packages.length === 0) {
1205
- process.stderr.write((0, color_1.dim)(" No package changes detected.\n"));
1206
- process.exit(0);
1207
- }
1208
- // Filter allowlist
1209
- const packages = discovery.packages.filter((p) => !config.allowlist.includes(p.name));
1210
- if (packages.length === 0) {
1211
- process.stderr.write((0, color_1.dim)(" All changed packages are allowlisted.\n"));
1212
- process.exit(0);
1213
- }
1214
- process.stderr.write((0, color_1.dim)(` Scanning ${packages.length} package${packages.length !== 1 ? "s" : ""} (${discovery.method})...\n`));
1215
- if (discovery.skipped.length > 0) {
1216
- process.stderr.write((0, color_1.yellow)(` Warning: ${discovery.skipped.length} package(s) skipped (max-packages=${config.maxPackages})\n`));
1217
- }
1218
- dbg(`packages to scan: ${packages.map(p => `${p.name}@${p.version}`).slice(0, 10).join(", ")}${packages.length > 10 ? ` (+${packages.length - 10} more)` : ""}`);
1219
- // Call Detection API
1220
- const startMs = Date.now();
1221
- const result = await (0, api_1.callAnalyzeAPI)(packages, config, (done, total) => {
1222
- process.stderr.write((0, color_1.dim)(` Analyzed ${done}/${total}...\n`));
1223
- });
1224
- dbg(`API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`);
1225
- // Render output
1226
- const output = (0, output_1.renderResult)(result, config);
1227
- process.stdout.write(output + "\n");
1228
- // Exit code
1229
- if (result.action === "block" && config.mode === "block") {
1230
- process.exit(2);
1231
- }
1232
- else if (result.action === "block" || result.action === "warn") {
1233
- process.exit(1);
1234
- }
1235
- process.exit(0);
1236
- }
1237
- main().catch((err) => {
1238
- process.stderr.write(`\n ${(0, color_1.bold)((0, color_1.red)("Error:"))} ${err.message}\n\n`);
1239
- process.exit(3); // Exit 3 = internal error (distinct from exit 1 = warnings)
1240
- });
1241
-
1242
- })();
1243
-
1244
- module.exports = __webpack_exports__;
1245
- /******/ })()
1246
- ;