devmap 0.3.5__py3-none-any.whl → 0.3.7__py3-none-any.whl

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/_devmap.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.3.5";
5
+ var VERSION = "0.3.7";
6
6
 
7
7
  // src/install.ts
8
8
  function isIsolated(i) {
@@ -662,6 +662,194 @@ function runScaAudit(cwd) {
662
662
  return checks;
663
663
  }
664
664
 
665
+ // src/health/parse.ts
666
+ function parsePipCheck(text) {
667
+ const lines = (text || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
668
+ const clean = lines.some((l) => /^no broken requirements found/i.test(l));
669
+ const conflicts = lines.filter((l) => !/^no broken requirements found/i.test(l));
670
+ if (clean || conflicts.length === 0) {
671
+ return { status: "ok", summary: "pip dependencies consistent" };
672
+ }
673
+ return {
674
+ status: "warn",
675
+ summary: `pip has ${conflicts.length} broken dependency requirement${conflicts.length === 1 ? "" : "s"}`,
676
+ detail: conflicts.join(`
677
+ `)
678
+ };
679
+ }
680
+ function parseNpmDoctor(text) {
681
+ const t = (text || "").trim();
682
+ if (!t)
683
+ return { status: "skipped", summary: "npm doctor produced no output (timed out or silent)" };
684
+ const lines = t.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
685
+ const NET_RE = /ECONNREFUSED|ETIMEDOUT|ENOTFOUND|getaddrinfo|EAI_|network request|registry\.npmjs\.org|request to https?:/i;
686
+ const BOILER_RE = /complete log of this run|^\s+at\s|node:internal|file:\/\//i;
687
+ const netLines = lines.filter((l) => NET_RE.test(l));
688
+ const failLines = lines.filter((l) => /npm error|✗|✖/i.test(l) && !BOILER_RE.test(l));
689
+ const realFails = failLines.filter((l) => !NET_RE.test(l));
690
+ if (realFails.length) {
691
+ return {
692
+ status: "warn",
693
+ summary: `npm doctor reported ${realFails.length} issue${realFails.length === 1 ? "" : "s"}`,
694
+ detail: realFails.join(`
695
+ `)
696
+ };
697
+ }
698
+ if (netLines.length) {
699
+ return { status: "skipped", summary: "npm doctor skipped (registry/network unreachable)" };
700
+ }
701
+ return { status: "ok", summary: "npm doctor passed" };
702
+ }
703
+ function parseRustupCheck(text) {
704
+ const t = (text || "").trim();
705
+ if (!t)
706
+ return { status: "skipped", summary: "rustup check produced no output (timed out or silent)" };
707
+ const lines = t.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
708
+ const updates = lines.filter((l) => /update available/i.test(l));
709
+ const upToDate = lines.filter((l) => /up to date/i.test(l));
710
+ if (updates.length) {
711
+ return {
712
+ status: "info",
713
+ summary: `rustup: ${updates.length} toolchain update${updates.length === 1 ? "" : "s"} available`,
714
+ detail: updates.join(`
715
+ `)
716
+ };
717
+ }
718
+ if (upToDate.length)
719
+ return { status: "ok", summary: "rustup toolchains up to date" };
720
+ return { status: "ok", summary: "rustup check passed" };
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
+ }
759
+
760
+ // src/health/scan.ts
761
+ var NETWORK_PROBES = [
762
+ {
763
+ id: "npm",
764
+ label: "npm",
765
+ kind: "mismatch",
766
+ network: true,
767
+ candidates: [["npm", ["doctor"]]],
768
+ timeout: 20000,
769
+ parse: parseNpmDoctor
770
+ },
771
+ {
772
+ id: "rustup",
773
+ label: "rustup",
774
+ kind: "stale",
775
+ network: true,
776
+ candidates: [["rustup", ["check"]]],
777
+ timeout: 15000,
778
+ parse: parseRustupCheck
779
+ }
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
+ }
798
+ function pickCandidate(p) {
799
+ const dirs = pathDirs();
800
+ for (const [cmd, args] of p.candidates) {
801
+ if (whichInDirs(cmd, dirs).length > 0)
802
+ return [cmd, args];
803
+ }
804
+ return null;
805
+ }
806
+ function toCheckNetwork(p, r) {
807
+ const sev = SEV_OF[r.status];
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 || "");
810
+ }
811
+ async function computeHealth(opts = {}) {
812
+ const checks = [];
813
+ const network = opts.healthNetwork === true;
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) {
838
+ if (p.network && !network) {
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).`));
840
+ continue;
841
+ }
842
+ const picked = pickCandidate(p);
843
+ if (!picked)
844
+ continue;
845
+ const [cmd, args] = picked;
846
+ const out = await runAsync(cmd, args, p.timeout);
847
+ const result = p.parse(out);
848
+ checks.push(toCheckNetwork(p, result));
849
+ }
850
+ return checks;
851
+ }
852
+
665
853
  // src/output/json.ts
666
854
  function buildManifest(byName, conflicts, checks, environments, shells) {
667
855
  const sys = byName.system || {};
@@ -832,6 +1020,46 @@ class ScanProgress {
832
1020
  }
833
1021
 
834
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
+ }
835
1063
  async function runScan(shells, disabled, opts = {}) {
836
1064
  const progress = opts.silent ? undefined : new ScanProgress(0, process.stderr.isTTY);
837
1065
  const ctx = {
@@ -841,7 +1069,8 @@ async function runScan(shells, disabled, opts = {}) {
841
1069
  progress,
842
1070
  checkOutdated: opts.checkOutdated,
843
1071
  wslDeep: opts.wslDeep,
844
- auditDeps: opts.auditDeps
1072
+ auditDeps: opts.auditDeps,
1073
+ healthNetwork: opts.healthNetwork
845
1074
  };
846
1075
  const { byName, allChecks } = await runDetectors(ctx, progress);
847
1076
  const groups = [
@@ -850,12 +1079,18 @@ async function runScan(shells, disabled, opts = {}) {
850
1079
  ...byName.ides || []
851
1080
  ];
852
1081
  const conflicts = computeConflicts(groups, shells, disabled);
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 });
853
1088
  const securityChecks = [
854
1089
  ...computeSecurity(byName),
855
1090
  ...computeScaInventory(),
856
1091
  ...opts.auditDeps ? runScaAudit(process.cwd()) : []
857
1092
  ];
858
- const collected = [...allChecks, ...conflicts.checks, ...securityChecks].filter((c) => !isCheckDisabled(c.ID, disabled));
1093
+ const collected = [...allChecks, ...conflicts.checks, ...securityChecks, ...healthChecks].filter((c) => !isCheckDisabled(c.ID, disabled));
859
1094
  const environments = {
860
1095
  vcpkg: byName.vcpkg,
861
1096
  pip: byName.pip,
@@ -1742,7 +1977,7 @@ register({
1742
1977
 
1743
1978
  // src/detector/projects.ts
1744
1979
  import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3 } from "node:fs";
1745
- 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";
1746
1981
  function isVenv(dir) {
1747
1982
  if (!existsSync5(join6(dir, "pyvenv.cfg")))
1748
1983
  return false;
@@ -1785,7 +2020,7 @@ function walk(root, maxDepth, out, seen) {
1785
2020
  if (e === ".venv" || e === "venv") {
1786
2021
  if (isVenv(full) && !seen.has(full.toLowerCase())) {
1787
2022
  seen.add(full.toLowerCase());
1788
- out.push({ project: basename3(dirname(full)), type: "venv", path: full });
2023
+ out.push({ project: basename3(dirname2(full)), type: "venv", path: full });
1789
2024
  }
1790
2025
  continue;
1791
2026
  }
@@ -1906,7 +2141,8 @@ async function cmdScan(opts, pos) {
1906
2141
  silent: opts.quiet === true,
1907
2142
  checkOutdated: opts.outdated === true,
1908
2143
  wslDeep: opts["wsl-deep"] === true,
1909
- auditDeps: opts["audit-deps"] === true
2144
+ auditDeps: opts["audit-deps"] === true,
2145
+ healthNetwork: opts["health-network"] === true
1910
2146
  });
1911
2147
  if (opts.json) {
1912
2148
  emitText(JSON.stringify(manifest, null, 2), opts.bom === true);
@@ -1963,13 +2199,25 @@ async function cmdQuery(opts, pos) {
1963
2199
  const c = result.conflicts;
1964
2200
  console.log(` \x1B[1mconflicts\x1B[0m multiVersion=${c.multiVersionTools?.length || 0} path=${c.pathConflicts?.length || 0} ghost=${c.ghostBinaries?.length || 0}`);
1965
2201
  }
1966
- if (result.checks && result.checks.length) {
1967
- console.log(` \x1B[1missues (${result.checks.length})\x1B[0m`);
1968
- for (const c of result.checks) {
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) {
1969
2209
  const sev = sevColor(c.Severity);
1970
2210
  console.log(` ${sev}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
1971
2211
  }
1972
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
+ }
1973
2221
  if (!result.groups.length && !result.checks.length && !result.conflicts) {
1974
2222
  console.log(" (no match)");
1975
2223
  }
@@ -2039,6 +2287,13 @@ function sevColor(sev) {
2039
2287
  return "\x1B[0m";
2040
2288
  }
2041
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
+ }
2042
2297
  function renderPrettyFromManifest(m) {
2043
2298
  const sys = m.system || {};
2044
2299
  console.log(`\x1B[1mdevmap\x1B[0m v${m.meta?.version || VERSION} \x1B[2m${sys.hostname || "?"}\x1B[0m ${sys.platform}/${sys.arch}`);
@@ -2053,13 +2308,26 @@ function renderPrettyFromManifest(m) {
2053
2308
  }
2054
2309
  }
2055
2310
  }
2056
- const checks = m.checks || [];
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");
2057
2315
  renderEnvironments(m.environments);
2058
- if (checks.length) {
2316
+ if (issues.length || infos.length || oks.length) {
2059
2317
  console.log(" " + "─".repeat(40));
2060
- console.log(` \x1B[1missues (${checks.length})\x1B[0m`);
2061
- for (const c of checks)
2062
- console.log(` ${sevColor(c.Severity)}${c.Severity}\x1B[0m ${c.ID} ${c.Label}`);
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
+ }
2063
2331
  }
2064
2332
  }
2065
2333
  var plur = (n, one, many = one + "s") => `${n} ${n === 1 ? one : many}`;
@@ -2141,11 +2409,25 @@ function renderMarkdownLite(m) {
2141
2409
  }
2142
2410
  lines.push("");
2143
2411
  }
2144
- if ((m.checks || []).length) {
2145
- lines.push(`## issues (${m.checks.length})`);
2146
- for (const c of m.checks)
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)
2419
+ lines.push(`- [${c.Severity}] \`${c.ID}\` ${c.Label} — ${c.Detail}`);
2420
+ }
2421
+ if (infos.length) {
2422
+ lines.push(`## info (${infos.length})`);
2423
+ for (const c of infos)
2147
2424
  lines.push(`- [${c.Severity}] \`${c.ID}\` ${c.Label} — ${c.Detail}`);
2148
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
+ }
2149
2431
  const env = m.environments || {};
2150
2432
  const envLines = [];
2151
2433
  if (env.vcpkg?.installed)
@@ -2193,6 +2475,7 @@ scan options:
2193
2475
  --wsl-deep probe inside stopped WSL distros (default probes only running ones to avoid waking distros)
2194
2476
  --ci CI gate: clean → exit 0; any warn → exit 2; any error → exit 1
2195
2477
  --audit-deps dependency CVE audit via OSV-Scanner/pip-audit/govulncheck/Trivy (opt-in, uses network)
2478
+ --health-network run network health probes too (npm doctor / rustup check; off by default to stay offline-fast)
2196
2479
  --quiet suppress scan progress feedback (for scripts/CI)
2197
2480
  --bom prepend UTF-8 BOM for viewing in Notepad etc. (only on redirect/pipe)
2198
2481
  --version / --help
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devmap
3
- Version: 0.3.5
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,6 +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, **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`.
43
44
  - **Three sub-commands**: `scan` (the fact source), `query` (ask the manifest), `devdiff` (compare two manifests — environment drift / cross-machine consistency).
44
45
  - **Zero Python dependencies.** The bundled app is pure TypeScript compiled to JavaScript.
45
46
 
@@ -0,0 +1,7 @@
1
+ devmap/__init__.py,sha256=4Ek7Di0FYYl76FKnHJ8VYiJPHlUTfoqyBB_hul_pFbk,1728
2
+ devmap/_devmap.mjs,sha256=w7VO5KhcO5teX7Acxhzm0fkwgww4l_w8CdppITgUQLg,89897
3
+ devmap-0.3.7.dist-info/METADATA,sha256=4clRMEm08Dk11EJfoEsZu5tddrK9XC22DC3km3YFVoI,6977
4
+ devmap-0.3.7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
+ devmap-0.3.7.dist-info/entry_points.txt,sha256=FOQKZCLz0WGWemrTw0suYRqKWrUB4_XJ2Gkrw60ZEcg,39
6
+ devmap-0.3.7.dist-info/top_level.txt,sha256=QeomiD_JKcjSd_jTZv0WJF3darUb2sUsxmocjgYJ6Qc,7
7
+ devmap-0.3.7.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- devmap/__init__.py,sha256=4Ek7Di0FYYl76FKnHJ8VYiJPHlUTfoqyBB_hul_pFbk,1728
2
- devmap/_devmap.mjs,sha256=xRTpuwBHdOAWySpx4TEudvxS0aIA288NYmluaXyoEAw,79539
3
- devmap-0.3.5.dist-info/METADATA,sha256=mwnfHI_6L1uNkqqjinecVYDiy0vtTWSS0fguLrtLk5s,6536
4
- devmap-0.3.5.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
5
- devmap-0.3.5.dist-info/entry_points.txt,sha256=FOQKZCLz0WGWemrTw0suYRqKWrUB4_XJ2Gkrw60ZEcg,39
6
- devmap-0.3.5.dist-info/top_level.txt,sha256=QeomiD_JKcjSd_jTZv0WJF3darUb2sUsxmocjgYJ6Qc,7
7
- devmap-0.3.5.dist-info/RECORD,,
File without changes