devmap 0.2.5__tar.gz → 0.3.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devmap
3
- Version: 0.2.5
3
+ Version: 0.3.2
4
4
  Summary: Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone.
5
5
  Author: devmap contributors
6
6
  License: MIT
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
3
 
4
- // devmap/src/version.ts
5
- var VERSION = "0.2.5";
4
+ // src/version.ts
5
+ var VERSION = "0.3.2";
6
6
 
7
- // devmap/src/install.ts
7
+ // src/install.ts
8
8
  function isIsolated(i) {
9
9
  return !i.onPath && !i.env;
10
10
  }
@@ -21,7 +21,7 @@ function locationTag(i) {
21
21
  return i.env ? `env: ${i.env}` : "off-path";
22
22
  }
23
23
 
24
- // devmap/src/detector/registry.ts
24
+ // src/detector/registry.ts
25
25
  var registry = [];
26
26
  function register(d) {
27
27
  registry.push(d);
@@ -48,7 +48,7 @@ async function runDetectors(ctx, progress) {
48
48
  return { byName, allChecks };
49
49
  }
50
50
 
51
- // devmap/src/check/model.ts
51
+ // src/check/model.ts
52
52
  function makeCheck(id, kind, severity, label, detail, path = [], fix) {
53
53
  const c = { ID: id, Kind: kind, Severity: severity, Label: label, Detail: detail, Path: path };
54
54
  if (fix)
@@ -56,7 +56,7 @@ function makeCheck(id, kind, severity, label, detail, path = [], fix) {
56
56
  return c;
57
57
  }
58
58
 
59
- // devmap/src/platform/exec.ts
59
+ // src/platform/exec.ts
60
60
  import { spawnSync, spawn } from "node:child_process";
61
61
  import { readdirSync, statSync, lstatSync, existsSync } from "node:fs";
62
62
  import { join, basename } from "node:path";
@@ -239,8 +239,8 @@ function whichInDirs(name, dirs) {
239
239
  }
240
240
  return out;
241
241
  }
242
- var SEMVER_RE = /(?<![\d.])\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.]+)?(?![\d.])/;
243
- var LOOSE_VERSION_RE = /(?<![\d.])\d+\.\d+(?![\d.])/;
242
+ var SEMVER_RE = /(?<![\d.])(\d+\.\d+\.\d+)(?:[-.+][0-9A-Za-z._-]+)*(?![\d.])/;
243
+ var LOOSE_VERSION_RE = /(?<![\d.])(\d+\.\d+)(?:[-.+][0-9A-Za-z._-]+)*(?![\d.])/;
244
244
  function extractVersion(out, binPath) {
245
245
  const lines = out.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("["));
246
246
  if (binPath) {
@@ -319,7 +319,7 @@ function scanBrokenSymlinks(dirs) {
319
319
  return ghosts;
320
320
  }
321
321
 
322
- // devmap/src/platform/shells.ts
322
+ // src/platform/shells.ts
323
323
  function shellName(s) {
324
324
  return { current: "current", powershell: "PowerShell", gitbash: "Git Bash", wsl: "WSL" }[s];
325
325
  }
@@ -349,7 +349,7 @@ function shellPathDirs(shell) {
349
349
  return pathDirs();
350
350
  }
351
351
 
352
- // devmap/src/detector/conflicts.ts
352
+ // src/detector/conflicts.ts
353
353
  function computeConflicts(groups, shells, disabled) {
354
354
  const checks = [];
355
355
  const multiVersionTools = [];
@@ -440,7 +440,220 @@ function isCheckDisabled(id, disabled) {
440
440
  return isDisabled(id, disabled);
441
441
  }
442
442
 
443
- // devmap/src/output/json.ts
443
+ // src/security/runtimeAdvisories.ts
444
+ var RUNTIME_ADVISORIES = {
445
+ node: {
446
+ eolByVersion: {
447
+ "16": "2023-09-11",
448
+ "18": "2025-04-30",
449
+ "20": "2026-04-30",
450
+ "22": "2027-04-30",
451
+ "24": "2028-04-30",
452
+ "26": "2029-04-30"
453
+ }
454
+ },
455
+ python: {
456
+ eolByVersion: {
457
+ "3.7": "2023-06-27",
458
+ "3.8": "2024-10-07",
459
+ "3.9": "2025-10-31",
460
+ "3.10": "2026-10-31",
461
+ "3.11": "2027-10-31",
462
+ "3.12": "2028-10-31",
463
+ "3.13": "2029-10-31"
464
+ }
465
+ },
466
+ go: {
467
+ eolByVersion: {
468
+ "1.20": "2024-02-06",
469
+ "1.21": "2024-08-13",
470
+ "1.22": "2025-02-11",
471
+ "1.23": "2025-08-12",
472
+ "1.24": "2026-02-11"
473
+ }
474
+ }
475
+ };
476
+
477
+ // src/security/scan.ts
478
+ function eolKeys(version) {
479
+ const parts = version.split(".");
480
+ const maj = parts[0];
481
+ const min = parts[1] ?? "0";
482
+ return [maj, `${maj}.${min}`];
483
+ }
484
+ function computeSecurity(byName) {
485
+ const checks = [];
486
+ const runtimes = byName.runtimes || [];
487
+ const now = Date.now();
488
+ for (const g of runtimes) {
489
+ const adv = RUNTIME_ADVISORIES[g.id];
490
+ if (!adv)
491
+ continue;
492
+ for (const inst of g.installs) {
493
+ const v = inst.version;
494
+ if (!v)
495
+ continue;
496
+ for (const key of eolKeys(v)) {
497
+ const eol = adv.eolByVersion[key];
498
+ if (eol) {
499
+ const eolT = Date.parse(eol + "T00:00:00Z");
500
+ if (!isNaN(eolT) && eolT < now) {
501
+ checks.push(makeCheck(`security.eol.${g.id}`, "stale", "warn", `${g.id} ${v} is end-of-life (since ${eol})`, `${g.id} ${v} reached end-of-life on ${eol} and no longer receives security patches. Upgrade to a supported release. (${inst.path})`, [inst.path]));
502
+ }
503
+ break;
504
+ }
505
+ }
506
+ }
507
+ }
508
+ return checks;
509
+ }
510
+
511
+ // src/security/sca.ts
512
+ import { spawnSync as spawnSync2 } from "child_process";
513
+ function whichTool(name) {
514
+ const isWin = process.platform === "win32";
515
+ const r = spawnSync2(isWin ? "where" : "command", isWin ? [name] : ["-v", name], { encoding: "utf-8" });
516
+ if (r.error)
517
+ return null;
518
+ const out = (r.stdout || "").split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
519
+ return out || null;
520
+ }
521
+ function getVersion(toolId) {
522
+ const r = spawnSync2(toolId, ["--version"], { encoding: "utf-8", timeout: 15000 });
523
+ if (r.error || !r.stdout)
524
+ return null;
525
+ const m = r.stdout.match(/(\d+\.\d+\.\d+)/);
526
+ return m ? m[1] : null;
527
+ }
528
+ function toSeverity(s) {
529
+ const t = (s || "").toUpperCase();
530
+ if (t === "CRITICAL" || t === "HIGH")
531
+ return "error";
532
+ return "warn";
533
+ }
534
+ var SCA_TOOLS = [
535
+ {
536
+ id: "osv-scanner",
537
+ label: "OSV-Scanner",
538
+ auditArgs: ["--format", "json", "."],
539
+ parse: (json) => {
540
+ const out = [];
541
+ for (const res of json?.results || []) {
542
+ for (const v of res?.vulnerabilities || []) {
543
+ const pkg = v?.package || {};
544
+ const sev = toSeverity(v?.severity || v?.database_specific?.severity);
545
+ const vid = v?.id || "UNKNOWN";
546
+ out.push(makeCheck(`security.deps.osv.${vid}`, "vuln", sev, `${pkg.name || "?"}@${pkg.version || "?"} affected by ${vid} (${sev === "error" ? "HIGH" : "MEDIUM"})`, `${vid}: ${v?.summary || v?.details || ""}. Package ${pkg.name}@${pkg.version}.`, [pkg.name || ""]));
547
+ }
548
+ }
549
+ return out;
550
+ }
551
+ },
552
+ {
553
+ id: "pip-audit",
554
+ label: "pip-audit",
555
+ auditArgs: ["-f", "json"],
556
+ parse: (json) => {
557
+ const out = [];
558
+ for (const dep of json?.dependencies || []) {
559
+ for (const v of dep?.vulns || []) {
560
+ const sev = toSeverity(v?.severity);
561
+ const vid = v?.id || "UNKNOWN";
562
+ out.push(makeCheck(`security.deps.pipaudit.${vid}`, "vuln", sev, `${dep?.name || "?"}@${dep?.version || "?"} affected by ${vid} (${sev === "error" ? "HIGH" : "MEDIUM"})`, `${vid}: ${v?.description || ""}. Fix versions: ${(v?.fix_versions || []).join(", ") || "n/a"}.`, [dep?.name || ""]));
563
+ }
564
+ }
565
+ for (const v of json?.vulnerabilities || []) {
566
+ const sev = toSeverity(v?.severity);
567
+ const vid = v?.id || "UNKNOWN";
568
+ out.push(makeCheck(`security.deps.pipaudit.${vid}`, "vuln", sev, `${v?.name || "?"} affected by ${vid}`, `${vid}: ${v?.description || ""}.`, [v?.name || ""]));
569
+ }
570
+ return out;
571
+ }
572
+ },
573
+ {
574
+ id: "govulncheck",
575
+ label: "govulncheck",
576
+ auditArgs: ["-json", "."],
577
+ parse: (json) => {
578
+ const out = [];
579
+ for (const res of json?.Results || []) {
580
+ for (const v of res?.Vulnerabilities || []) {
581
+ const score = v?.Severity?.Score;
582
+ const sev = typeof score === "number" && score >= 9 ? "error" : "warn";
583
+ const vid = v?.OSV?.id || "UNKNOWN";
584
+ const pkg = v?.Package?.Path || v?.Package?.Name || "?";
585
+ out.push(makeCheck(`security.deps.govulncheck.${vid}`, "vuln", sev, `${pkg}@${v?.Version || "?"} affected by ${vid} (${sev === "error" ? "HIGH" : "MEDIUM"})`, `${vid}: ${v?.Details || ""}. Package ${pkg}@${v?.Version || "?"}.`, [pkg]));
586
+ }
587
+ }
588
+ return out;
589
+ }
590
+ },
591
+ {
592
+ id: "trivy",
593
+ label: "Trivy",
594
+ auditArgs: ["fs", "--format", "json", "."],
595
+ parse: (json) => {
596
+ const out = [];
597
+ for (const res of json?.Results || []) {
598
+ for (const v of res?.Vulnerabilities || []) {
599
+ const sev = toSeverity(v?.Severity);
600
+ const vid = v?.VulnerabilityID || "UNKNOWN";
601
+ out.push(makeCheck(`security.deps.trivy.${vid}`, "vuln", sev, `${v?.PkgName || "?"}@${v?.InstalledVersion || "?"} affected by ${vid} (${sev === "error" ? "HIGH" : "MEDIUM"})`, `${vid}: ${v?.Title || v?.Description || ""}. Fixed in ${v?.FixedVersion || "n/a"}.`, [v?.PkgName || ""]));
602
+ }
603
+ }
604
+ return out;
605
+ }
606
+ }
607
+ ];
608
+ function detectScaTools() {
609
+ return SCA_TOOLS.map((t) => {
610
+ const p = whichTool(t.id);
611
+ return { id: t.id, label: t.label, path: p, version: p ? getVersion(t.id) : null };
612
+ });
613
+ }
614
+ function computeScaInventory() {
615
+ return detectScaTools().map((d) => {
616
+ const id = `security.scanner.${d.id}`;
617
+ if (d.path) {
618
+ return makeCheck(id, "vuln", "info", `${d.label} available (${d.version || "?"})`, `${d.label} is installed at ${d.path}. devmap can shell out to it for dependency CVE audit with --audit-deps.`, [d.path || ""]);
619
+ }
620
+ return makeCheck(id, "vuln", "info", `${d.label} not installed`, `${d.label} not found on PATH. Install it to enable dependency CVE scanning via devmap --audit-deps (it fetches vuln DBs over the network).`, []);
621
+ });
622
+ }
623
+ function runScaAudit(cwd) {
624
+ const present = detectScaTools().filter((d) => d.path);
625
+ if (present.length === 0) {
626
+ return [
627
+ makeCheck("security.deps.none", "vuln", "info", "no SCA tool installed — dependency CVE audit skipped", "Install osv-scanner / pip-audit / govulncheck / trivy to enable dependency CVE scanning via `devmap --audit-deps`.", [])
628
+ ];
629
+ }
630
+ const checks = [];
631
+ for (const d of present) {
632
+ const tool = SCA_TOOLS.find((t) => t.id === d.id);
633
+ const r = spawnSync2(d.id, tool.auditArgs, { cwd, encoding: "utf-8", timeout: 120000, maxBuffer: 16 * 1024 * 1024 });
634
+ if (r.error || r.status === null) {
635
+ checks.push(makeCheck(`security.deps.${d.id}.error`, "vuln", "info", `${d.label} audit failed to start`, `${d.label} could not be launched: ${r.error?.message || "unknown error"}.`, []));
636
+ continue;
637
+ }
638
+ if (r.status !== 0 && !r.stdout.trim()) {
639
+ checks.push(makeCheck(`security.deps.${d.id}.skip`, "vuln", "info", `${d.label} audit produced no output (exit ${r.status})`, `${d.label} returned no scannable result in ${cwd} (no project / no lockfile / no module).`, []));
640
+ continue;
641
+ }
642
+ try {
643
+ const json = JSON.parse(r.stdout);
644
+ const parsed = tool.parse(json, d.label);
645
+ checks.push(...parsed);
646
+ if (parsed.length === 0) {
647
+ checks.push(makeCheck(`security.deps.${d.id}.clean`, "vuln", "info", `${d.label} audit: no vulnerabilities found`, `${d.label} found no known vulnerabilities in ${cwd}.`, []));
648
+ }
649
+ } catch (e) {
650
+ checks.push(makeCheck(`security.deps.${d.id}.parse`, "vuln", "info", `${d.label} audit output not JSON`, `${d.label} returned output that could not be parsed as JSON: ${e?.message || e}.`, []));
651
+ }
652
+ }
653
+ return checks;
654
+ }
655
+
656
+ // src/output/json.ts
444
657
  function buildManifest(byName, conflicts, checks, environments, shells) {
445
658
  const sys = byName.system || {};
446
659
  const runtimes = byName.runtimes || [];
@@ -487,7 +700,7 @@ function buildManifest(byName, conflicts, checks, environments, shells) {
487
700
  };
488
701
  }
489
702
 
490
- // devmap/src/glyphs.ts
703
+ // src/glyphs.ts
491
704
  var WIN = process.platform === "win32";
492
705
  var G = {
493
706
  spinner: WIN ? ["|", "/", "-", "\\"] : ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
@@ -521,7 +734,7 @@ function clean(s) {
521
734
  return out;
522
735
  }
523
736
 
524
- // devmap/src/progress.ts
737
+ // src/progress.ts
525
738
  var SPINNERS = G.spinner;
526
739
 
527
740
  class ScanProgress {
@@ -609,7 +822,7 @@ class ScanProgress {
609
822
  }
610
823
  }
611
824
 
612
- // devmap/src/scan.ts
825
+ // src/scan.ts
613
826
  async function runScan(shells, disabled, opts = {}) {
614
827
  const progress = opts.silent ? undefined : new ScanProgress(0, process.stderr.isTTY);
615
828
  const ctx = {
@@ -618,7 +831,8 @@ async function runScan(shells, disabled, opts = {}) {
618
831
  projectRoots: opts.projectRoots,
619
832
  progress,
620
833
  checkOutdated: opts.checkOutdated,
621
- wslDeep: opts.wslDeep
834
+ wslDeep: opts.wslDeep,
835
+ auditDeps: opts.auditDeps
622
836
  };
623
837
  const { byName, allChecks } = await runDetectors(ctx, progress);
624
838
  const groups = [
@@ -627,7 +841,12 @@ async function runScan(shells, disabled, opts = {}) {
627
841
  ...byName.ides || []
628
842
  ];
629
843
  const conflicts = computeConflicts(groups, shells, disabled);
630
- const collected = [...allChecks, ...conflicts.checks].filter((c) => !isCheckDisabled(c.ID, disabled));
844
+ const securityChecks = [
845
+ ...computeSecurity(byName),
846
+ ...computeScaInventory(),
847
+ ...opts.auditDeps ? runScaAudit(process.cwd()) : []
848
+ ];
849
+ const collected = [...allChecks, ...conflicts.checks, ...securityChecks].filter((c) => !isCheckDisabled(c.ID, disabled));
631
850
  const environments = {
632
851
  vcpkg: byName.vcpkg,
633
852
  pip: byName.pip,
@@ -643,7 +862,7 @@ async function runScan(shells, disabled, opts = {}) {
643
862
  return buildManifest(byName, conflicts, collected, environments, shells);
644
863
  }
645
864
 
646
- // devmap/src/query.ts
865
+ // src/query.ts
647
866
  var CAT_ALIASES = {
648
867
  runtimes: "runtime",
649
868
  runtime: "runtime",
@@ -704,7 +923,7 @@ function queryManifest(m, opts) {
704
923
  return { groups, checks, conflicts };
705
924
  }
706
925
 
707
- // devmap/src/diff.ts
926
+ // src/diff.ts
708
927
  var CAT_KEYS = [
709
928
  ["runtimes", "runtime"],
710
929
  ["packageManagers", "packageManager"],
@@ -777,7 +996,7 @@ function diffManifests(a, b) {
777
996
  return { tools: toolList, issues, summary };
778
997
  }
779
998
 
780
- // devmap/src/detector/system.ts
999
+ // src/detector/system.ts
781
1000
  import os from "node:os";
782
1001
  register({
783
1002
  name: "system",
@@ -796,12 +1015,12 @@ register({
796
1015
  }
797
1016
  });
798
1017
 
799
- // devmap/src/detector/runtimes.ts
1018
+ // src/detector/runtimes.ts
800
1019
  import { homedir } from "node:os";
801
1020
  import { join as join2 } from "node:path";
802
1021
  import { existsSync as existsSync2, readdirSync as readdirSync2 } from "node:fs";
803
1022
 
804
- // devmap/src/detector/util.ts
1023
+ // src/detector/util.ts
805
1024
  var CONCURRENCY = 12;
806
1025
  var GENERIC_FLAGS = ["--version", "version", "-v", "-V"];
807
1026
  function dedupe(paths, seen) {
@@ -868,7 +1087,7 @@ async function detectSimpleAsync(list, onProgress) {
868
1087
  return out;
869
1088
  }
870
1089
 
871
- // devmap/src/detector/runtimes.ts
1090
+ // src/detector/runtimes.ts
872
1091
  var HOME = homedir();
873
1092
  async function condaPythonPaths() {
874
1093
  const found = [];
@@ -939,7 +1158,7 @@ register({
939
1158
  }
940
1159
  });
941
1160
 
942
- // devmap/src/detector/pkgmgrs.ts
1161
+ // src/detector/pkgmgrs.ts
943
1162
  var TOOLS = [
944
1163
  "npm",
945
1164
  "pip",
@@ -968,7 +1187,7 @@ register({
968
1187
  }
969
1188
  });
970
1189
 
971
- // devmap/src/detector/ides.ts
1190
+ // src/detector/ides.ts
972
1191
  register({
973
1192
  name: "ides",
974
1193
  async scan(ctx) {
@@ -979,7 +1198,7 @@ register({
979
1198
  }
980
1199
  });
981
1200
 
982
- // devmap/src/detector/vcpkg.ts
1201
+ // src/detector/vcpkg.ts
983
1202
  import { join as join3 } from "node:path";
984
1203
  var CANDIDATES = [
985
1204
  "C:\\vcpkg\\vcpkg.exe",
@@ -1038,7 +1257,7 @@ register({
1038
1257
  }
1039
1258
  });
1040
1259
 
1041
- // devmap/src/detector/piputil.ts
1260
+ // src/detector/piputil.ts
1042
1261
  function parseList2(raw) {
1043
1262
  if (!raw)
1044
1263
  return [];
@@ -1072,7 +1291,7 @@ async function pipPackagesAsync(pythonPath, timeout = 6000, withOutdated = false
1072
1291
  }
1073
1292
  }
1074
1293
 
1075
- // devmap/src/detector/pip.ts
1294
+ // src/detector/pip.ts
1076
1295
  import { basename as basename2 } from "node:path";
1077
1296
  function shortName(pythonPath) {
1078
1297
  const lower = pythonPath.toLowerCase();
@@ -1126,7 +1345,7 @@ register({
1126
1345
  }
1127
1346
  });
1128
1347
 
1129
- // devmap/src/detector/apt.ts
1348
+ // src/detector/apt.ts
1130
1349
  function parseApt(out) {
1131
1350
  const pkgs = [];
1132
1351
  for (const line of out.split(`
@@ -1156,7 +1375,7 @@ register({
1156
1375
  }
1157
1376
  });
1158
1377
 
1159
- // devmap/src/detector/condaenv.ts
1378
+ // src/detector/condaenv.ts
1160
1379
  import { join as join4 } from "node:path";
1161
1380
  import { existsSync as existsSync3 } from "node:fs";
1162
1381
  function pythonOf(envPath) {
@@ -1201,7 +1420,7 @@ register({
1201
1420
  }
1202
1421
  });
1203
1422
 
1204
- // devmap/src/detector/docker.ts
1423
+ // src/detector/docker.ts
1205
1424
  var DESKTOP = "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe";
1206
1425
  function findDocker() {
1207
1426
  for (const p of whichInDirs("docker", pathDirs()))
@@ -1246,7 +1465,7 @@ register({
1246
1465
  }
1247
1466
  });
1248
1467
 
1249
- // devmap/src/detector/wsl.ts
1468
+ // src/detector/wsl.ts
1250
1469
  var PROBE_SCRIPT = "echo py3=$(command -v python3); echo py=$(command -v python); echo node=$(command -v node); " + "echo go=$(command -v go); echo rustc=$(command -v rustc); echo gcc=$(command -v gcc); " + "echo git=$(command -v git); echo docker=$(command -v docker)";
1251
1470
  var TOOL_KEYS = ["py3", "py", "node", "go", "rustc", "gcc", "git", "docker"];
1252
1471
  async function probeDistro(name) {
@@ -1340,7 +1559,7 @@ register({
1340
1559
  }
1341
1560
  });
1342
1561
 
1343
- // devmap/src/detector/vsshells.ts
1562
+ // src/detector/vsshells.ts
1344
1563
  import { readdirSync as readdirSync3, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
1345
1564
  import { join as join5 } from "node:path";
1346
1565
  var VS_BASES = [
@@ -1506,7 +1725,7 @@ register({
1506
1725
  }
1507
1726
  });
1508
1727
 
1509
- // devmap/src/detector/projects.ts
1728
+ // src/detector/projects.ts
1510
1729
  import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3 } from "node:fs";
1511
1730
  import { join as join6, basename as basename3, dirname } from "node:path";
1512
1731
  function isVenv(dir) {
@@ -1580,7 +1799,7 @@ register({
1580
1799
  }
1581
1800
  });
1582
1801
 
1583
- // devmap/src/cli.ts
1802
+ // src/cli.ts
1584
1803
  import { writeFileSync, mkdirSync, readFileSync, existsSync as existsSync6 } from "node:fs";
1585
1804
  import { homedir as homedir2 } from "node:os";
1586
1805
  import { join as join7 } from "node:path";
@@ -1617,7 +1836,7 @@ function tokenize(argv) {
1617
1836
  const key = a.slice(2);
1618
1837
  const nxt = argv[i + 1];
1619
1838
  if (nxt !== undefined && !nxt.startsWith("--")) {
1620
- opts[key] = nxt;
1839
+ opts[key] = opts[key] !== undefined ? `${opts[key]},${nxt}` : nxt;
1621
1840
  i++;
1622
1841
  } else
1623
1842
  opts[key] = true;
@@ -1671,7 +1890,8 @@ async function cmdScan(opts, pos) {
1671
1890
  projectRoots: roots,
1672
1891
  silent: opts.quiet === true,
1673
1892
  checkOutdated: opts.outdated === true,
1674
- wslDeep: opts["wsl-deep"] === true
1893
+ wslDeep: opts["wsl-deep"] === true,
1894
+ auditDeps: opts["audit-deps"] === true
1675
1895
  });
1676
1896
  if (opts.json) {
1677
1897
  emitText(JSON.stringify(manifest, null, 2), opts.bom === true);
@@ -1687,8 +1907,16 @@ async function cmdScan(opts, pos) {
1687
1907
  } catch {}
1688
1908
  }
1689
1909
  }
1690
- const hasError = (manifest.checks || []).some((c) => c.Severity === "error");
1691
- return opts.ci && hasError ? 1 : 0;
1910
+ if (opts.ci) {
1911
+ const hasError = (manifest.checks || []).some((c) => c.Severity === "error");
1912
+ if (hasError)
1913
+ return 1;
1914
+ const hasWarn = (manifest.checks || []).some((c) => c.Severity === "warn");
1915
+ if (hasWarn)
1916
+ return 2;
1917
+ return 0;
1918
+ }
1919
+ return 0;
1692
1920
  }
1693
1921
  async function cmdQuery(opts, pos) {
1694
1922
  const selector = pos[1];
@@ -1948,7 +2176,8 @@ scan options:
1948
2176
  --scan-root <dir> project-local environment scan root (comma-separated; default cwd). e.g. --scan-root C:\\src
1949
2177
  --outdated check pip/conda upgradable packages online (off by default: requires PyPI access and slows the scan)
1950
2178
  --wsl-deep probe inside stopped WSL distros (default probes only running ones to avoid waking distros)
1951
- --ci any error-level issue → exit code 1
2179
+ --ci CI gate: clean → exit 0; any warn → exit 2; any error → exit 1
2180
+ --audit-deps dependency CVE audit via OSV-Scanner/pip-audit/govulncheck/Trivy (opt-in, uses network)
1952
2181
  --quiet suppress scan progress feedback (for scripts/CI)
1953
2182
  --bom prepend UTF-8 BOM for viewing in Notepad etc. (only on redirect/pipe)
1954
2183
  --version / --help
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devmap
3
- Version: 0.2.5
3
+ Version: 0.3.2
4
4
  Summary: Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone.
5
5
  Author: devmap contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devmap"
7
- version = "0.2.5"
7
+ version = "0.3.2"
8
8
  description = "Map your machine's dev environment and surface issues (PATH conflicts, ghost binaries, multi-version collisions). fastfetch-for-dev-tools. This PyPI package ships the real devmap CLI (a bundled JavaScript app launched via Node.js or Bun), so `pip install devmap && devmap` works standalone."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes
File without changes