@westbayberry/dg 1.0.1 → 1.0.3
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 +1 -1
- package/dist/index.js +369 -12
- package/dist/index.mjs +40477 -0
- package/package.json +13 -5
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -124,7 +124,10 @@ async function callAnalyzeBatch(packages, config) {
|
|
|
124
124
|
if (error instanceof Error && error.name === "AbortError") {
|
|
125
125
|
throw new APIError("Request timed out after 120s. Try scanning fewer packages.", 408, "");
|
|
126
126
|
}
|
|
127
|
-
|
|
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}`);
|
|
128
131
|
}
|
|
129
132
|
clearTimeout(timeoutId);
|
|
130
133
|
if (response.status === 401) {
|
|
@@ -177,15 +180,37 @@ exports.getVersion = getVersion;
|
|
|
177
180
|
const node_util_1 = __nccwpck_require__(975);
|
|
178
181
|
const node_fs_1 = __nccwpck_require__(24);
|
|
179
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
|
+
}
|
|
180
201
|
const USAGE = `
|
|
181
202
|
Dependency Guardian — Supply chain security scanner
|
|
182
203
|
|
|
183
204
|
Usage:
|
|
184
205
|
dependency-guardian scan [options]
|
|
185
206
|
dg scan [options]
|
|
207
|
+
dg npm install <pkg> [npm-flags]
|
|
208
|
+
dg wrap
|
|
186
209
|
|
|
187
210
|
Commands:
|
|
188
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
|
|
189
214
|
|
|
190
215
|
Options:
|
|
191
216
|
--api-key <key> API key (or set DG_API_KEY env var)
|
|
@@ -198,25 +223,36 @@ const USAGE = `
|
|
|
198
223
|
--json Output JSON for CI parsing
|
|
199
224
|
--scan-all Scan all packages, not just changed
|
|
200
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
|
|
201
228
|
--help Show this help message
|
|
202
229
|
--version Show version number
|
|
203
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
|
+
|
|
204
235
|
Environment Variables:
|
|
205
236
|
DG_API_KEY API key
|
|
206
237
|
DG_API_URL API base URL
|
|
207
238
|
DG_MODE Mode (block/warn/off)
|
|
208
239
|
DG_ALLOWLIST Comma-separated allowlist
|
|
240
|
+
DG_DEBUG Enable debug output (set to 1)
|
|
209
241
|
|
|
210
242
|
Exit Codes:
|
|
211
243
|
0 pass — No risks detected
|
|
212
244
|
1 warn — Risks detected (advisory)
|
|
213
245
|
2 block — High-risk packages detected
|
|
246
|
+
3 error — Internal error (API failure, config error)
|
|
214
247
|
|
|
215
248
|
Examples:
|
|
216
249
|
DG_API_KEY=dg_live_xxx dg scan
|
|
217
250
|
dg scan --api-key dg_live_xxx --json
|
|
218
251
|
dg scan --scan-all --mode block
|
|
219
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
|
|
220
256
|
`.trimStart();
|
|
221
257
|
exports.USAGE = USAGE;
|
|
222
258
|
function getVersion() {
|
|
@@ -242,6 +278,8 @@ function parseConfig(argv) {
|
|
|
242
278
|
json: { type: "boolean", default: false },
|
|
243
279
|
"scan-all": { type: "boolean", default: false },
|
|
244
280
|
"base-lockfile": { type: "string" },
|
|
281
|
+
debug: { type: "boolean", default: false },
|
|
282
|
+
"no-config": { type: "boolean", default: false },
|
|
245
283
|
help: { type: "boolean", default: false },
|
|
246
284
|
version: { type: "boolean", default: false },
|
|
247
285
|
},
|
|
@@ -257,11 +295,14 @@ function parseConfig(argv) {
|
|
|
257
295
|
process.exit(0);
|
|
258
296
|
}
|
|
259
297
|
const command = positionals[0] ?? "scan";
|
|
298
|
+
const noConfig = values["no-config"];
|
|
299
|
+
const dgrc = noConfig ? {} : loadDgrc();
|
|
260
300
|
if (values["api-key"]) {
|
|
261
301
|
process.stderr.write("Warning: --api-key is deprecated (visible in process list). Use DG_API_KEY env var instead.\n");
|
|
262
302
|
}
|
|
263
303
|
const apiKey = values["api-key"] ??
|
|
264
304
|
process.env.DG_API_KEY ??
|
|
305
|
+
dgrc.apiKey ??
|
|
265
306
|
"";
|
|
266
307
|
if (!apiKey) {
|
|
267
308
|
process.stderr.write("Error: API key required. Set DG_API_KEY environment variable.\n" +
|
|
@@ -271,6 +312,7 @@ function parseConfig(argv) {
|
|
|
271
312
|
}
|
|
272
313
|
const modeRaw = values.mode ??
|
|
273
314
|
process.env.DG_MODE ??
|
|
315
|
+
dgrc.mode ??
|
|
274
316
|
"warn";
|
|
275
317
|
if (!["block", "warn", "off"].includes(modeRaw)) {
|
|
276
318
|
process.stderr.write(`Error: Invalid mode "${modeRaw}". Must be block, warn, or off.\n`);
|
|
@@ -279,23 +321,40 @@ function parseConfig(argv) {
|
|
|
279
321
|
const allowlistRaw = values.allowlist ??
|
|
280
322
|
process.env.DG_ALLOWLIST ??
|
|
281
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
|
+
}
|
|
282
340
|
return {
|
|
283
341
|
apiKey,
|
|
284
342
|
apiUrl: values["api-url"] ??
|
|
285
343
|
process.env.DG_API_URL ??
|
|
344
|
+
dgrc.apiUrl ??
|
|
286
345
|
"https://api.westbayberry.com",
|
|
287
346
|
mode: modeRaw,
|
|
288
|
-
blockThreshold
|
|
289
|
-
warnThreshold
|
|
290
|
-
maxPackages
|
|
347
|
+
blockThreshold,
|
|
348
|
+
warnThreshold,
|
|
349
|
+
maxPackages,
|
|
291
350
|
allowlist: allowlistRaw
|
|
292
|
-
.split(",")
|
|
293
|
-
.
|
|
294
|
-
.filter(Boolean),
|
|
351
|
+
? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
352
|
+
: (dgrc.allowlist ?? []),
|
|
295
353
|
json: values.json,
|
|
296
354
|
scanAll: values["scan-all"],
|
|
297
355
|
baseLockfile: values["base-lockfile"] ?? null,
|
|
298
356
|
command,
|
|
357
|
+
debug,
|
|
299
358
|
};
|
|
300
359
|
}
|
|
301
360
|
|
|
@@ -473,6 +532,273 @@ function toPackageInput(change) {
|
|
|
473
532
|
}
|
|
474
533
|
|
|
475
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
|
+
|
|
476
802
|
/***/ }),
|
|
477
803
|
|
|
478
804
|
/***/ 202:
|
|
@@ -535,9 +861,10 @@ function renderResult(result, config) {
|
|
|
535
861
|
lines.push("");
|
|
536
862
|
return lines.join("\n");
|
|
537
863
|
}
|
|
538
|
-
// Package table
|
|
539
|
-
const
|
|
540
|
-
const
|
|
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)));
|
|
541
868
|
const COL_SCORE = 7;
|
|
542
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")}`);
|
|
543
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))}`);
|
|
@@ -718,7 +1045,7 @@ function parseLockfile(content) {
|
|
|
718
1045
|
if (path === "")
|
|
719
1046
|
continue;
|
|
720
1047
|
const name = extractPackageName(path);
|
|
721
|
-
if (name) {
|
|
1048
|
+
if (name && !packages.has(name)) {
|
|
722
1049
|
const e = entry;
|
|
723
1050
|
packages.set(name, {
|
|
724
1051
|
version: e.version ?? "",
|
|
@@ -775,6 +1102,13 @@ module.exports = require("node:fs");
|
|
|
775
1102
|
|
|
776
1103
|
/***/ }),
|
|
777
1104
|
|
|
1105
|
+
/***/ 161:
|
|
1106
|
+
/***/ ((module) => {
|
|
1107
|
+
|
|
1108
|
+
module.exports = require("node:os");
|
|
1109
|
+
|
|
1110
|
+
/***/ }),
|
|
1111
|
+
|
|
778
1112
|
/***/ 760:
|
|
779
1113
|
/***/ ((module) => {
|
|
780
1114
|
|
|
@@ -838,15 +1172,35 @@ const lockfile_1 = __nccwpck_require__(746);
|
|
|
838
1172
|
const api_1 = __nccwpck_require__(879);
|
|
839
1173
|
const output_1 = __nccwpck_require__(202);
|
|
840
1174
|
const color_1 = __nccwpck_require__(988);
|
|
1175
|
+
const npm_wrapper_1 = __nccwpck_require__(124);
|
|
841
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
|
+
}
|
|
842
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
|
+
};
|
|
843
1194
|
if (config.mode === "off") {
|
|
844
1195
|
process.stderr.write((0, color_1.dim)(" Dependency Guardian: mode is off — skipping.\n"));
|
|
845
1196
|
process.exit(0);
|
|
846
1197
|
}
|
|
1198
|
+
dbg(`mode=${config.mode} block=${config.blockThreshold} warn=${config.warnThreshold} max=${config.maxPackages}`);
|
|
1199
|
+
dbg(`api=${config.apiUrl}`);
|
|
847
1200
|
// Discover packages
|
|
848
1201
|
process.stderr.write((0, color_1.dim)(" Discovering package changes...\n"));
|
|
849
1202
|
const discovery = (0, lockfile_1.discoverChanges)(process.cwd(), config);
|
|
1203
|
+
dbg(`discovery method: ${discovery.method}`);
|
|
850
1204
|
if (discovery.packages.length === 0) {
|
|
851
1205
|
process.stderr.write((0, color_1.dim)(" No package changes detected.\n"));
|
|
852
1206
|
process.exit(0);
|
|
@@ -861,10 +1215,13 @@ async function main() {
|
|
|
861
1215
|
if (discovery.skipped.length > 0) {
|
|
862
1216
|
process.stderr.write((0, color_1.yellow)(` Warning: ${discovery.skipped.length} package(s) skipped (max-packages=${config.maxPackages})\n`));
|
|
863
1217
|
}
|
|
1218
|
+
dbg(`packages to scan: ${packages.map(p => `${p.name}@${p.version}`).slice(0, 10).join(", ")}${packages.length > 10 ? ` (+${packages.length - 10} more)` : ""}`);
|
|
864
1219
|
// Call Detection API
|
|
1220
|
+
const startMs = Date.now();
|
|
865
1221
|
const result = await (0, api_1.callAnalyzeAPI)(packages, config, (done, total) => {
|
|
866
1222
|
process.stderr.write((0, color_1.dim)(` Analyzed ${done}/${total}...\n`));
|
|
867
1223
|
});
|
|
1224
|
+
dbg(`API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`);
|
|
868
1225
|
// Render output
|
|
869
1226
|
const output = (0, output_1.renderResult)(result, config);
|
|
870
1227
|
process.stdout.write(output + "\n");
|
|
@@ -879,7 +1236,7 @@ async function main() {
|
|
|
879
1236
|
}
|
|
880
1237
|
main().catch((err) => {
|
|
881
1238
|
process.stderr.write(`\n ${(0, color_1.bold)((0, color_1.red)("Error:"))} ${err.message}\n\n`);
|
|
882
|
-
process.exit(1)
|
|
1239
|
+
process.exit(3); // Exit 3 = internal error (distinct from exit 1 = warnings)
|
|
883
1240
|
});
|
|
884
1241
|
|
|
885
1242
|
})();
|