devmap 0.3.6__tar.gz → 0.3.7__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.
- {devmap-0.3.6 → devmap-0.3.7}/PKG-INFO +2 -2
- {devmap-0.3.6 → devmap-0.3.7}/README.md +1 -1
- {devmap-0.3.6 → devmap-0.3.7}/devmap/_devmap.mjs +192 -45
- {devmap-0.3.6 → devmap-0.3.7}/devmap.egg-info/PKG-INFO +2 -2
- {devmap-0.3.6 → devmap-0.3.7}/pyproject.toml +1 -1
- {devmap-0.3.6 → devmap-0.3.7}/devmap/__init__.py +0 -0
- {devmap-0.3.6 → devmap-0.3.7}/devmap.egg-info/SOURCES.txt +0 -0
- {devmap-0.3.6 → devmap-0.3.7}/devmap.egg-info/dependency_links.txt +0 -0
- {devmap-0.3.6 → devmap-0.3.7}/devmap.egg-info/entry_points.txt +0 -0
- {devmap-0.3.6 → devmap-0.3.7}/devmap.egg-info/top_level.txt +0 -0
- {devmap-0.3.6 → devmap-0.3.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devmap
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
@@ -40,7 +40,7 @@ Requirements at runtime: a Node.js (or Bun) runtime available on `PATH`. Python
|
|
|
40
40
|
- **Off-PATH awareness**: reports installed-but-not-on-PATH tools, while correctly treating version-manager environments (conda envs, nvm versions) as by-design off-PATH.
|
|
41
41
|
- **Environment layers**: vcpkg packages, per-python pip packages (incl. upgradable), apt packages (Linux), conda environments, Docker (binary + daemon + images/containers), WSL distros, Visual Studio dev shells, and project-local environments (`.venv`, `node_modules`).
|
|
42
42
|
- **Dependency auditing (optional)**: with `--audit-deps`, devmap detects and invokes installed SCA tools (osv-scanner, govulncheck, pip-audit, Trivy), parses their JSON, and merges results into a unified view. devmap is a *scanner/orchestrator*, not a CVE database — for authoritative vulnerability data, use those tools directly.
|
|
43
|
-
- **Health layer**: converges official self-checks into one view
|
|
43
|
+
- **Health layer**: converges official self-checks into one view, **per environment**. Offline by default: `pip check` runs for **every** Python interpreter, and `conda doctor` runs for **every** conda env. With `--health-network`, `npm doctor` / `rustup check` are added. Missing/offline probes degrade gracefully to `info` (skipped). Only `warn`/`error` surface under `issues (N)`; passing checks are summarized as `✓ N checks passed`.
|
|
44
44
|
- **Three sub-commands**: `scan` (the fact source), `query` (ask the manifest), `devdiff` (compare two manifests — environment drift / cross-machine consistency).
|
|
45
45
|
- **Zero Python dependencies.** The bundled app is pure TypeScript compiled to JavaScript.
|
|
46
46
|
|
|
@@ -22,7 +22,7 @@ Requirements at runtime: a Node.js (or Bun) runtime available on `PATH`. Python
|
|
|
22
22
|
- **Off-PATH awareness**: reports installed-but-not-on-PATH tools, while correctly treating version-manager environments (conda envs, nvm versions) as by-design off-PATH.
|
|
23
23
|
- **Environment layers**: vcpkg packages, per-python pip packages (incl. upgradable), apt packages (Linux), conda environments, Docker (binary + daemon + images/containers), WSL distros, Visual Studio dev shells, and project-local environments (`.venv`, `node_modules`).
|
|
24
24
|
- **Dependency auditing (optional)**: with `--audit-deps`, devmap detects and invokes installed SCA tools (osv-scanner, govulncheck, pip-audit, Trivy), parses their JSON, and merges results into a unified view. devmap is a *scanner/orchestrator*, not a CVE database — for authoritative vulnerability data, use those tools directly.
|
|
25
|
-
- **Health layer**: converges official self-checks into one view
|
|
25
|
+
- **Health layer**: converges official self-checks into one view, **per environment**. Offline by default: `pip check` runs for **every** Python interpreter, and `conda doctor` runs for **every** conda env. With `--health-network`, `npm doctor` / `rustup check` are added. Missing/offline probes degrade gracefully to `info` (skipped). Only `warn`/`error` surface under `issues (N)`; passing checks are summarized as `✓ N checks passed`.
|
|
26
26
|
- **Three sub-commands**: `scan` (the fact source), `query` (ask the manifest), `devdiff` (compare two manifests — environment drift / cross-machine consistency).
|
|
27
27
|
- **Zero Python dependencies.** The bundled app is pure TypeScript compiled to JavaScript.
|
|
28
28
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/version.ts
|
|
5
|
-
var VERSION = "0.3.
|
|
5
|
+
var VERSION = "0.3.7";
|
|
6
6
|
|
|
7
7
|
// src/install.ts
|
|
8
8
|
function isIsolated(i) {
|
|
@@ -719,23 +719,46 @@ function parseRustupCheck(text) {
|
|
|
719
719
|
return { status: "ok", summary: "rustup toolchains up to date" };
|
|
720
720
|
return { status: "ok", summary: "rustup check passed" };
|
|
721
721
|
}
|
|
722
|
+
function parseCondaDoctor(text) {
|
|
723
|
+
const t = (text || "").trim();
|
|
724
|
+
if (!t)
|
|
725
|
+
return { status: "skipped", summary: "conda doctor produced no output (timed out or silent)" };
|
|
726
|
+
const lines = t.split(/\r?\n/).map((l) => l.trim());
|
|
727
|
+
const problemSections = [];
|
|
728
|
+
let cur = null;
|
|
729
|
+
for (const line of lines) {
|
|
730
|
+
if (!line)
|
|
731
|
+
continue;
|
|
732
|
+
const hdr = line.match(/^[❌✅]\s*(.+?)\s*:?\s*$/);
|
|
733
|
+
if (hdr) {
|
|
734
|
+
const name = hdr[1].replace(/:\s*$/, "").trim();
|
|
735
|
+
cur = { name, items: 0 };
|
|
736
|
+
if (line.startsWith("❌"))
|
|
737
|
+
problemSections.push(cur);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (cur) {
|
|
741
|
+
const m = line.match(/:\s*(\d+)\s*$/);
|
|
742
|
+
if (m)
|
|
743
|
+
cur.items += parseInt(m[1], 10);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const real = problemSections.filter((p) => p.items > 0);
|
|
747
|
+
if (real.length === 0) {
|
|
748
|
+
return { status: "ok", summary: "conda environment healthy", detail: t };
|
|
749
|
+
}
|
|
750
|
+
const total = real.reduce((a, p) => a + p.items, 0);
|
|
751
|
+
const detail = real.map((p) => `${p.name}: ${p.items}`).join(`
|
|
752
|
+
`);
|
|
753
|
+
return {
|
|
754
|
+
status: "warn",
|
|
755
|
+
summary: `conda doctor: ${real.length} check${real.length === 1 ? "" : "s"} failed (${total} affected files)`,
|
|
756
|
+
detail
|
|
757
|
+
};
|
|
758
|
+
}
|
|
722
759
|
|
|
723
760
|
// src/health/scan.ts
|
|
724
|
-
var
|
|
725
|
-
{
|
|
726
|
-
id: "pip",
|
|
727
|
-
label: "pip",
|
|
728
|
-
kind: "conflict",
|
|
729
|
-
network: false,
|
|
730
|
-
candidates: [
|
|
731
|
-
["pip", ["check"]],
|
|
732
|
-
["pip3", ["check"]],
|
|
733
|
-
["python", ["-m", "pip", "check"]],
|
|
734
|
-
["python3", ["-m", "pip", "check"]]
|
|
735
|
-
],
|
|
736
|
-
timeout: 15000,
|
|
737
|
-
parse: parsePipCheck
|
|
738
|
-
},
|
|
761
|
+
var NETWORK_PROBES = [
|
|
739
762
|
{
|
|
740
763
|
id: "npm",
|
|
741
764
|
label: "npm",
|
|
@@ -755,6 +778,23 @@ var PROBES = [
|
|
|
755
778
|
parse: parseRustupCheck
|
|
756
779
|
}
|
|
757
780
|
];
|
|
781
|
+
var SEV_OF = {
|
|
782
|
+
ok: "ok",
|
|
783
|
+
warn: "warn",
|
|
784
|
+
info: "info",
|
|
785
|
+
skipped: "info"
|
|
786
|
+
};
|
|
787
|
+
function skippedCheck(prefix, kind, summary) {
|
|
788
|
+
return makeCheck(`health.${prefix}.skipped`, kind, "info", summary, "");
|
|
789
|
+
}
|
|
790
|
+
function toCheckPerEnv(prefix, envLabel, r, kind, path) {
|
|
791
|
+
if (r.status === "skipped") {
|
|
792
|
+
return makeCheck(`health.${prefix}.${envLabel}.skipped`, kind, "info", `${prefix} doctor (${envLabel}) produced no output`, "");
|
|
793
|
+
}
|
|
794
|
+
const id = `health.${prefix}.${envLabel}.check`;
|
|
795
|
+
const summary = r.status === "ok" ? `${prefix} check (${envLabel}): consistent` : r.summary;
|
|
796
|
+
return makeCheck(id, kind, SEV_OF[r.status], summary, r.detail || "", path ? [path] : []);
|
|
797
|
+
}
|
|
758
798
|
function pickCandidate(p) {
|
|
759
799
|
const dirs = pathDirs();
|
|
760
800
|
for (const [cmd, args] of p.candidates) {
|
|
@@ -763,33 +803,49 @@ function pickCandidate(p) {
|
|
|
763
803
|
}
|
|
764
804
|
return null;
|
|
765
805
|
}
|
|
766
|
-
|
|
767
|
-
ok: "ok",
|
|
768
|
-
warn: "warn",
|
|
769
|
-
info: "info",
|
|
770
|
-
skipped: "info"
|
|
771
|
-
};
|
|
772
|
-
function toCheck(p, r) {
|
|
806
|
+
function toCheckNetwork(p, r) {
|
|
773
807
|
const sev = SEV_OF[r.status];
|
|
774
|
-
const id = `health.${p.id}.check`;
|
|
775
|
-
|
|
776
|
-
return makeCheck(realId, p.kind, sev, r.summary, r.detail || "", [], r.status === "ok" ? undefined : undefined);
|
|
808
|
+
const id = r.status === "skipped" ? `health.${p.id}.skipped` : `health.${p.id}.check`;
|
|
809
|
+
return makeCheck(id, p.kind, sev, r.summary, r.detail || "");
|
|
777
810
|
}
|
|
778
811
|
async function computeHealth(opts = {}) {
|
|
779
812
|
const checks = [];
|
|
780
813
|
const network = opts.healthNetwork === true;
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
814
|
+
const pythons = opts.pythons || [];
|
|
815
|
+
if (pythons.length === 0) {
|
|
816
|
+
checks.push(skippedCheck("pip", "conflict", "no python interpreters detected (pip check skipped)"));
|
|
817
|
+
} else {
|
|
818
|
+
await Promise.all(pythons.map(async (py) => {
|
|
819
|
+
const out = await runBinAsync(py.path, ["-m", "pip", "check"], 15000);
|
|
820
|
+
const r = parsePipCheck(out);
|
|
821
|
+
checks.push(toCheckPerEnv("pip", py.label, r, "conflict", py.path));
|
|
822
|
+
}));
|
|
823
|
+
}
|
|
824
|
+
const condaEnvs = opts.condaEnvs || [];
|
|
825
|
+
const condaOnPath = whichInDirs("conda", pathDirs()).length > 0;
|
|
826
|
+
if (!condaOnPath) {
|
|
827
|
+
checks.push(skippedCheck("conda", "stale", "conda not on PATH (conda doctor skipped)"));
|
|
828
|
+
} else if (condaEnvs.length === 0) {
|
|
829
|
+
checks.push(skippedCheck("conda", "stale", "no conda environments detected (conda doctor skipped)"));
|
|
830
|
+
} else {
|
|
831
|
+
await Promise.all(condaEnvs.map(async (e) => {
|
|
832
|
+
const out = await runAsync("conda", ["doctor", "-n", e.name], 30000);
|
|
833
|
+
const r = parseCondaDoctor(out);
|
|
834
|
+
checks.push(toCheckPerEnv("conda", e.name, r, "stale", e.pythonPath || undefined));
|
|
835
|
+
}));
|
|
836
|
+
}
|
|
837
|
+
for (const p of NETWORK_PROBES) {
|
|
785
838
|
if (p.network && !network) {
|
|
786
839
|
checks.push(makeCheck(`health.${p.id}.skipped`, p.kind, "info", `${p.label} doctor skipped (offline by default)`, `Run with --health-network to invoke ${p.label} self-checks (they require network).`));
|
|
787
840
|
continue;
|
|
788
841
|
}
|
|
842
|
+
const picked = pickCandidate(p);
|
|
843
|
+
if (!picked)
|
|
844
|
+
continue;
|
|
789
845
|
const [cmd, args] = picked;
|
|
790
846
|
const out = await runAsync(cmd, args, p.timeout);
|
|
791
847
|
const result = p.parse(out);
|
|
792
|
-
checks.push(
|
|
848
|
+
checks.push(toCheckNetwork(p, result));
|
|
793
849
|
}
|
|
794
850
|
return checks;
|
|
795
851
|
}
|
|
@@ -964,6 +1020,46 @@ class ScanProgress {
|
|
|
964
1020
|
}
|
|
965
1021
|
|
|
966
1022
|
// src/scan.ts
|
|
1023
|
+
import { realpathSync } from "node:fs";
|
|
1024
|
+
import { dirname } from "node:path";
|
|
1025
|
+
function healthPythonLabel(path) {
|
|
1026
|
+
const lower = path.toLowerCase().replace(/\\/g, "/");
|
|
1027
|
+
const env = lower.match(/[\\/]envs[\\/]([^\\/]+)[\\/]/);
|
|
1028
|
+
if (env)
|
|
1029
|
+
return env[1];
|
|
1030
|
+
if (/miniconda3[\\/]python/i.test(lower) || /anaconda3[\\/]python/i.test(lower))
|
|
1031
|
+
return "base";
|
|
1032
|
+
if (lower.includes("workbuddy/binaries/python"))
|
|
1033
|
+
return "managed";
|
|
1034
|
+
if (lower.includes("/nvm") || lower.includes("\\nvm"))
|
|
1035
|
+
return "nvm";
|
|
1036
|
+
const parts = lower.split("/").filter(Boolean);
|
|
1037
|
+
const parent = parts[parts.length - 2] || "";
|
|
1038
|
+
return parent || "python";
|
|
1039
|
+
}
|
|
1040
|
+
function dedupePythons(raw) {
|
|
1041
|
+
const byDir = new Map;
|
|
1042
|
+
for (const p of raw) {
|
|
1043
|
+
let rp = p.pythonPath;
|
|
1044
|
+
try {
|
|
1045
|
+
rp = realpathSync(p.pythonPath);
|
|
1046
|
+
} catch {}
|
|
1047
|
+
const dir = dirname(rp);
|
|
1048
|
+
if (!byDir.has(dir))
|
|
1049
|
+
byDir.set(dir, p.pythonPath);
|
|
1050
|
+
}
|
|
1051
|
+
const out = [];
|
|
1052
|
+
const used = new Set;
|
|
1053
|
+
for (const path of byDir.values()) {
|
|
1054
|
+
let label = healthPythonLabel(path);
|
|
1055
|
+
let n = 2;
|
|
1056
|
+
while (used.has(label))
|
|
1057
|
+
label = `${healthPythonLabel(path)}#${n++}`;
|
|
1058
|
+
used.add(label);
|
|
1059
|
+
out.push({ path, label });
|
|
1060
|
+
}
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
967
1063
|
async function runScan(shells, disabled, opts = {}) {
|
|
968
1064
|
const progress = opts.silent ? undefined : new ScanProgress(0, process.stderr.isTTY);
|
|
969
1065
|
const ctx = {
|
|
@@ -983,7 +1079,12 @@ async function runScan(shells, disabled, opts = {}) {
|
|
|
983
1079
|
...byName.ides || []
|
|
984
1080
|
];
|
|
985
1081
|
const conflicts = computeConflicts(groups, shells, disabled);
|
|
986
|
-
const
|
|
1082
|
+
const pythons = dedupePythons(byName.pip?.perInterpreter || []);
|
|
1083
|
+
const condaEnvs = (byName.condaenv?.envs || []).map((e) => ({
|
|
1084
|
+
name: e.name,
|
|
1085
|
+
pythonPath: e.pythonPath
|
|
1086
|
+
}));
|
|
1087
|
+
const healthChecks = await computeHealth({ healthNetwork: opts.healthNetwork, pythons, condaEnvs });
|
|
987
1088
|
const securityChecks = [
|
|
988
1089
|
...computeSecurity(byName),
|
|
989
1090
|
...computeScaInventory(),
|
|
@@ -1876,7 +1977,7 @@ register({
|
|
|
1876
1977
|
|
|
1877
1978
|
// src/detector/projects.ts
|
|
1878
1979
|
import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3 } from "node:fs";
|
|
1879
|
-
import { join as join6, basename as basename3, dirname } from "node:path";
|
|
1980
|
+
import { join as join6, basename as basename3, dirname as dirname2 } from "node:path";
|
|
1880
1981
|
function isVenv(dir) {
|
|
1881
1982
|
if (!existsSync5(join6(dir, "pyvenv.cfg")))
|
|
1882
1983
|
return false;
|
|
@@ -1919,7 +2020,7 @@ function walk(root, maxDepth, out, seen) {
|
|
|
1919
2020
|
if (e === ".venv" || e === "venv") {
|
|
1920
2021
|
if (isVenv(full) && !seen.has(full.toLowerCase())) {
|
|
1921
2022
|
seen.add(full.toLowerCase());
|
|
1922
|
-
out.push({ project: basename3(
|
|
2023
|
+
out.push({ project: basename3(dirname2(full)), type: "venv", path: full });
|
|
1923
2024
|
}
|
|
1924
2025
|
continue;
|
|
1925
2026
|
}
|
|
@@ -2098,13 +2199,25 @@ async function cmdQuery(opts, pos) {
|
|
|
2098
2199
|
const c = result.conflicts;
|
|
2099
2200
|
console.log(` \x1B[1mconflicts\x1B[0m multiVersion=${c.multiVersionTools?.length || 0} path=${c.pathConflicts?.length || 0} ghost=${c.ghostBinaries?.length || 0}`);
|
|
2100
2201
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2202
|
+
const allChecks = result.checks || [];
|
|
2203
|
+
const issues = allChecks.filter((c) => bucketOf(c.Severity) === "issue");
|
|
2204
|
+
const infos = allChecks.filter((c) => bucketOf(c.Severity) === "info");
|
|
2205
|
+
const oks = allChecks.filter((c) => bucketOf(c.Severity) === "ok");
|
|
2206
|
+
if (issues.length) {
|
|
2207
|
+
console.log(` \x1B[1missues (${issues.length})\x1B[0m`);
|
|
2208
|
+
for (const c of issues) {
|
|
2104
2209
|
const sev = sevColor(c.Severity);
|
|
2105
2210
|
console.log(` ${sev}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
|
|
2106
2211
|
}
|
|
2107
2212
|
}
|
|
2213
|
+
if (infos.length) {
|
|
2214
|
+
console.log(` \x1B[1minfo (${infos.length})\x1B[0m`);
|
|
2215
|
+
for (const c of infos)
|
|
2216
|
+
console.log(` ${sevColor(c.Severity)}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
|
|
2217
|
+
}
|
|
2218
|
+
if (oks.length) {
|
|
2219
|
+
console.log(` \x1B[32m✓ ${oks.length} checks passed\x1B[0m`);
|
|
2220
|
+
}
|
|
2108
2221
|
if (!result.groups.length && !result.checks.length && !result.conflicts) {
|
|
2109
2222
|
console.log(" (no match)");
|
|
2110
2223
|
}
|
|
@@ -2174,6 +2287,13 @@ function sevColor(sev) {
|
|
|
2174
2287
|
return "\x1B[0m";
|
|
2175
2288
|
}
|
|
2176
2289
|
}
|
|
2290
|
+
function bucketOf(sev) {
|
|
2291
|
+
if (sev === "error" || sev === "warn" || sev === "missing")
|
|
2292
|
+
return "issue";
|
|
2293
|
+
if (sev === "info")
|
|
2294
|
+
return "info";
|
|
2295
|
+
return "ok";
|
|
2296
|
+
}
|
|
2177
2297
|
function renderPrettyFromManifest(m) {
|
|
2178
2298
|
const sys = m.system || {};
|
|
2179
2299
|
console.log(`\x1B[1mdevmap\x1B[0m v${m.meta?.version || VERSION} \x1B[2m${sys.hostname || "?"}\x1B[0m ${sys.platform}/${sys.arch}`);
|
|
@@ -2188,13 +2308,26 @@ function renderPrettyFromManifest(m) {
|
|
|
2188
2308
|
}
|
|
2189
2309
|
}
|
|
2190
2310
|
}
|
|
2191
|
-
const
|
|
2311
|
+
const allChecks = m.checks || [];
|
|
2312
|
+
const issues = allChecks.filter((c) => bucketOf(c.Severity) === "issue");
|
|
2313
|
+
const infos = allChecks.filter((c) => bucketOf(c.Severity) === "info");
|
|
2314
|
+
const oks = allChecks.filter((c) => bucketOf(c.Severity) === "ok");
|
|
2192
2315
|
renderEnvironments(m.environments);
|
|
2193
|
-
if (
|
|
2316
|
+
if (issues.length || infos.length || oks.length) {
|
|
2194
2317
|
console.log(" " + "─".repeat(40));
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2318
|
+
if (issues.length) {
|
|
2319
|
+
console.log(` \x1B[1missues (${issues.length})\x1B[0m`);
|
|
2320
|
+
for (const c of issues)
|
|
2321
|
+
console.log(` ${sevColor(c.Severity)}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
|
|
2322
|
+
}
|
|
2323
|
+
if (infos.length) {
|
|
2324
|
+
console.log(` \x1B[1minfo (${infos.length})\x1B[0m`);
|
|
2325
|
+
for (const c of infos)
|
|
2326
|
+
console.log(` ${sevColor(c.Severity)}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
|
|
2327
|
+
}
|
|
2328
|
+
if (oks.length) {
|
|
2329
|
+
console.log(` \x1B[32m✓ ${oks.length} checks passed\x1B[0m`);
|
|
2330
|
+
}
|
|
2198
2331
|
}
|
|
2199
2332
|
}
|
|
2200
2333
|
var plur = (n, one, many = one + "s") => `${n} ${n === 1 ? one : many}`;
|
|
@@ -2276,11 +2409,25 @@ function renderMarkdownLite(m) {
|
|
|
2276
2409
|
}
|
|
2277
2410
|
lines.push("");
|
|
2278
2411
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2412
|
+
const allChecks = m.checks || [];
|
|
2413
|
+
const issues = allChecks.filter((c) => bucketOf(c.Severity) === "issue");
|
|
2414
|
+
const infos = allChecks.filter((c) => bucketOf(c.Severity) === "info");
|
|
2415
|
+
const oks = allChecks.filter((c) => bucketOf(c.Severity) === "ok");
|
|
2416
|
+
if (issues.length) {
|
|
2417
|
+
lines.push(`## issues (${issues.length})`);
|
|
2418
|
+
for (const c of issues)
|
|
2282
2419
|
lines.push(`- [${c.Severity}] \`${c.ID}\` ${c.Label} — ${c.Detail}`);
|
|
2283
2420
|
}
|
|
2421
|
+
if (infos.length) {
|
|
2422
|
+
lines.push(`## info (${infos.length})`);
|
|
2423
|
+
for (const c of infos)
|
|
2424
|
+
lines.push(`- [${c.Severity}] \`${c.ID}\` ${c.Label} — ${c.Detail}`);
|
|
2425
|
+
}
|
|
2426
|
+
if (oks.length) {
|
|
2427
|
+
lines.push(`## checks passed (${oks.length})`);
|
|
2428
|
+
for (const c of oks)
|
|
2429
|
+
lines.push(`- [${c.Severity}] \`${c.ID}\` ${c.Label}`);
|
|
2430
|
+
}
|
|
2284
2431
|
const env = m.environments || {};
|
|
2285
2432
|
const envLines = [];
|
|
2286
2433
|
if (env.vcpkg?.installed)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devmap
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
@@ -40,7 +40,7 @@ Requirements at runtime: a Node.js (or Bun) runtime available on `PATH`. Python
|
|
|
40
40
|
- **Off-PATH awareness**: reports installed-but-not-on-PATH tools, while correctly treating version-manager environments (conda envs, nvm versions) as by-design off-PATH.
|
|
41
41
|
- **Environment layers**: vcpkg packages, per-python pip packages (incl. upgradable), apt packages (Linux), conda environments, Docker (binary + daemon + images/containers), WSL distros, Visual Studio dev shells, and project-local environments (`.venv`, `node_modules`).
|
|
42
42
|
- **Dependency auditing (optional)**: with `--audit-deps`, devmap detects and invokes installed SCA tools (osv-scanner, govulncheck, pip-audit, Trivy), parses their JSON, and merges results into a unified view. devmap is a *scanner/orchestrator*, not a CVE database — for authoritative vulnerability data, use those tools directly.
|
|
43
|
-
- **Health layer**: converges official self-checks into one view
|
|
43
|
+
- **Health layer**: converges official self-checks into one view, **per environment**. Offline by default: `pip check` runs for **every** Python interpreter, and `conda doctor` runs for **every** conda env. With `--health-network`, `npm doctor` / `rustup check` are added. Missing/offline probes degrade gracefully to `info` (skipped). Only `warn`/`error` surface under `issues (N)`; passing checks are summarized as `✓ N checks passed`.
|
|
44
44
|
- **Three sub-commands**: `scan` (the fact source), `query` (ask the manifest), `devdiff` (compare two manifests — environment drift / cross-machine consistency).
|
|
45
45
|
- **Zero Python dependencies.** The bundled app is pure TypeScript compiled to JavaScript.
|
|
46
46
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devmap"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.7"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|