@neat.is/core 0.2.8 → 0.2.9
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/dist/{chunk-FTKDVKBC.js → chunk-5KX7EI4F.js} +2 -2
- package/dist/{chunk-HWO746IM.js → chunk-BVF7MVR5.js} +2 -2
- package/dist/{chunk-GAYTAGEH.js → chunk-IRPH6KL4.js} +266 -146
- package/dist/chunk-IRPH6KL4.js.map +1 -0
- package/dist/cli.cjs +356 -235
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +287 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/neatd.cjs +271 -151
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +277 -156
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-GAYTAGEH.js.map +0 -1
- /package/dist/{chunk-FTKDVKBC.js.map → chunk-5KX7EI4F.js.map} +0 -0
- /package/dist/{chunk-HWO746IM.js.map → chunk-BVF7MVR5.js.map} +0 -0
|
@@ -427,19 +427,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
427
427
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
428
428
|
let best = { path: [start], edges: [] };
|
|
429
429
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
430
|
-
function step(node,
|
|
431
|
-
if (
|
|
432
|
-
best = { path: [...
|
|
430
|
+
function step(node, path31, edges) {
|
|
431
|
+
if (path31.length > best.path.length) {
|
|
432
|
+
best = { path: [...path31], edges: [...edges] };
|
|
433
433
|
}
|
|
434
|
-
if (
|
|
434
|
+
if (path31.length - 1 >= maxDepth) return;
|
|
435
435
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
436
436
|
for (const [srcId, edge] of incoming) {
|
|
437
437
|
if (visited.has(srcId)) continue;
|
|
438
438
|
visited.add(srcId);
|
|
439
|
-
|
|
439
|
+
path31.push(srcId);
|
|
440
440
|
edges.push(edge);
|
|
441
|
-
step(srcId,
|
|
442
|
-
|
|
441
|
+
step(srcId, path31, edges);
|
|
442
|
+
path31.pop();
|
|
443
443
|
edges.pop();
|
|
444
444
|
visited.delete(srcId);
|
|
445
445
|
}
|
|
@@ -1561,10 +1561,10 @@ async function readErrorEvents(errorsPath) {
|
|
|
1561
1561
|
}
|
|
1562
1562
|
|
|
1563
1563
|
// src/extract/services.ts
|
|
1564
|
-
import { promises as
|
|
1565
|
-
import
|
|
1564
|
+
import { promises as fs7 } from "fs";
|
|
1565
|
+
import path7 from "path";
|
|
1566
1566
|
import ignore from "ignore";
|
|
1567
|
-
import { minimatch } from "minimatch";
|
|
1567
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
1568
1568
|
import { NodeType as NodeType4, serviceId as serviceId2 } from "@neat.is/types";
|
|
1569
1569
|
|
|
1570
1570
|
// src/extract/shared.ts
|
|
@@ -1675,6 +1675,72 @@ function pythonToPackage(service) {
|
|
|
1675
1675
|
};
|
|
1676
1676
|
}
|
|
1677
1677
|
|
|
1678
|
+
// src/extract/owners.ts
|
|
1679
|
+
import { promises as fs6 } from "fs";
|
|
1680
|
+
import path6 from "path";
|
|
1681
|
+
import { minimatch } from "minimatch";
|
|
1682
|
+
async function loadCodeowners(scanPath) {
|
|
1683
|
+
const candidates = [
|
|
1684
|
+
path6.join(scanPath, "CODEOWNERS"),
|
|
1685
|
+
path6.join(scanPath, ".github", "CODEOWNERS")
|
|
1686
|
+
];
|
|
1687
|
+
for (const file of candidates) {
|
|
1688
|
+
if (await exists(file)) {
|
|
1689
|
+
const raw = await fs6.readFile(file, "utf8");
|
|
1690
|
+
return parseCodeowners(raw);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
function parseCodeowners(raw) {
|
|
1696
|
+
const rules = [];
|
|
1697
|
+
for (const line of raw.split("\n")) {
|
|
1698
|
+
const trimmed = line.trim();
|
|
1699
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1700
|
+
const match = /^(\S+)\s+(.+)$/.exec(trimmed);
|
|
1701
|
+
if (!match) continue;
|
|
1702
|
+
rules.push({ pattern: match[1], owners: match[2].trim() });
|
|
1703
|
+
}
|
|
1704
|
+
return { rules };
|
|
1705
|
+
}
|
|
1706
|
+
function matchOwner(file, repoPath) {
|
|
1707
|
+
const normalized = repoPath.split(path6.sep).join("/");
|
|
1708
|
+
for (const rule of file.rules) {
|
|
1709
|
+
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
1710
|
+
}
|
|
1711
|
+
return null;
|
|
1712
|
+
}
|
|
1713
|
+
function matchesPattern(rawPattern, repoPath) {
|
|
1714
|
+
let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
|
|
1715
|
+
if (pattern === "*") return !repoPath.includes("/");
|
|
1716
|
+
if (pattern === "**" || pattern === "") return true;
|
|
1717
|
+
if (pattern.endsWith("/")) pattern = pattern + "**";
|
|
1718
|
+
if (minimatch(repoPath, pattern, { dot: true })) return true;
|
|
1719
|
+
if (!pattern.includes("*") && minimatch(repoPath, pattern + "/**", { dot: true })) return true;
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
async function readPackageJsonAuthor(serviceDir) {
|
|
1723
|
+
const pkgPath = path6.join(serviceDir, "package.json");
|
|
1724
|
+
if (!await exists(pkgPath)) return null;
|
|
1725
|
+
try {
|
|
1726
|
+
const pkg = await readJson(pkgPath);
|
|
1727
|
+
if (!pkg.author) return null;
|
|
1728
|
+
if (typeof pkg.author === "string") return pkg.author;
|
|
1729
|
+
if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
|
|
1730
|
+
return null;
|
|
1731
|
+
} catch {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
1736
|
+
if (codeowners && repoPath !== void 0) {
|
|
1737
|
+
const owner = matchOwner(codeowners, repoPath);
|
|
1738
|
+
if (owner) return owner;
|
|
1739
|
+
}
|
|
1740
|
+
const author = await readPackageJsonAuthor(serviceDir);
|
|
1741
|
+
return author ?? void 0;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1678
1744
|
// src/extract/services.ts
|
|
1679
1745
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
1680
1746
|
function parseScanDepth() {
|
|
@@ -1691,21 +1757,21 @@ function workspaceGlobs(pkg) {
|
|
|
1691
1757
|
return null;
|
|
1692
1758
|
}
|
|
1693
1759
|
async function loadGitignore(scanPath) {
|
|
1694
|
-
const gitignorePath =
|
|
1760
|
+
const gitignorePath = path7.join(scanPath, ".gitignore");
|
|
1695
1761
|
if (!await exists(gitignorePath)) return null;
|
|
1696
|
-
const raw = await
|
|
1762
|
+
const raw = await fs7.readFile(gitignorePath, "utf8");
|
|
1697
1763
|
return ignore().add(raw);
|
|
1698
1764
|
}
|
|
1699
1765
|
async function walkDirs(start, scanPath, options, visit) {
|
|
1700
1766
|
async function recurse(current, depth) {
|
|
1701
1767
|
if (depth > options.maxDepth) return;
|
|
1702
|
-
const entries = await
|
|
1768
|
+
const entries = await fs7.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1703
1769
|
for (const entry of entries) {
|
|
1704
1770
|
if (!entry.isDirectory()) continue;
|
|
1705
1771
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1706
|
-
const child =
|
|
1772
|
+
const child = path7.join(current, entry.name);
|
|
1707
1773
|
if (options.ig) {
|
|
1708
|
-
const rel =
|
|
1774
|
+
const rel = path7.relative(scanPath, child).split(path7.sep).join("/");
|
|
1709
1775
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
1710
1776
|
}
|
|
1711
1777
|
await visit(child);
|
|
@@ -1720,8 +1786,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1720
1786
|
for (const raw of globs) {
|
|
1721
1787
|
const pattern = raw.replace(/^\.\//, "");
|
|
1722
1788
|
if (!pattern.includes("*")) {
|
|
1723
|
-
const candidate =
|
|
1724
|
-
if (await exists(
|
|
1789
|
+
const candidate = path7.join(scanPath, pattern);
|
|
1790
|
+
if (await exists(path7.join(candidate, "package.json"))) found.add(candidate);
|
|
1725
1791
|
continue;
|
|
1726
1792
|
}
|
|
1727
1793
|
const segments = pattern.split("/");
|
|
@@ -1730,13 +1796,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1730
1796
|
if (seg.includes("*")) break;
|
|
1731
1797
|
staticSegments.push(seg);
|
|
1732
1798
|
}
|
|
1733
|
-
const start =
|
|
1799
|
+
const start = path7.join(scanPath, ...staticSegments);
|
|
1734
1800
|
if (!await exists(start)) continue;
|
|
1735
1801
|
const hasDoubleStar = pattern.includes("**");
|
|
1736
1802
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
1737
1803
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
1738
|
-
const rel =
|
|
1739
|
-
if (
|
|
1804
|
+
const rel = path7.relative(scanPath, dir).split(path7.sep).join("/");
|
|
1805
|
+
if (minimatch2(rel, pattern) && await exists(path7.join(dir, "package.json"))) {
|
|
1740
1806
|
found.add(dir);
|
|
1741
1807
|
}
|
|
1742
1808
|
});
|
|
@@ -1744,9 +1810,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1744
1810
|
return [...found];
|
|
1745
1811
|
}
|
|
1746
1812
|
async function discoverNodeService(scanPath, dir) {
|
|
1747
|
-
const pkgPath =
|
|
1813
|
+
const pkgPath = path7.join(dir, "package.json");
|
|
1748
1814
|
if (!await exists(pkgPath)) return null;
|
|
1749
|
-
|
|
1815
|
+
let pkg;
|
|
1816
|
+
try {
|
|
1817
|
+
pkg = await readJson(pkgPath);
|
|
1818
|
+
} catch (err) {
|
|
1819
|
+
console.warn(
|
|
1820
|
+
`[neat] services skipped ${path7.relative(scanPath, pkgPath)}: ${err.message}`
|
|
1821
|
+
);
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1750
1824
|
if (!pkg.name) return null;
|
|
1751
1825
|
const node = {
|
|
1752
1826
|
id: serviceId2(pkg.name),
|
|
@@ -1755,7 +1829,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
1755
1829
|
language: "javascript",
|
|
1756
1830
|
version: pkg.version,
|
|
1757
1831
|
dependencies: pkg.dependencies ?? {},
|
|
1758
|
-
repoPath:
|
|
1832
|
+
repoPath: path7.relative(scanPath, dir),
|
|
1759
1833
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
1760
1834
|
};
|
|
1761
1835
|
return { pkg, dir, node };
|
|
@@ -1771,13 +1845,22 @@ async function discoverPyService(scanPath, dir) {
|
|
|
1771
1845
|
language: "python",
|
|
1772
1846
|
version: py.version,
|
|
1773
1847
|
dependencies: py.dependencies,
|
|
1774
|
-
repoPath:
|
|
1848
|
+
repoPath: path7.relative(scanPath, dir)
|
|
1775
1849
|
};
|
|
1776
1850
|
return { pkg, dir, node };
|
|
1777
1851
|
}
|
|
1778
1852
|
async function discoverServices(scanPath) {
|
|
1779
|
-
const rootPkgPath =
|
|
1780
|
-
|
|
1853
|
+
const rootPkgPath = path7.join(scanPath, "package.json");
|
|
1854
|
+
let rootPkg = null;
|
|
1855
|
+
if (await exists(rootPkgPath)) {
|
|
1856
|
+
try {
|
|
1857
|
+
rootPkg = await readJson(rootPkgPath);
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
console.warn(
|
|
1860
|
+
`[neat] services workspaces skipped ${path7.relative(scanPath, rootPkgPath)}: ${err.message}`
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1781
1864
|
const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
|
|
1782
1865
|
const candidateDirs = [];
|
|
1783
1866
|
if (wsGlobs) {
|
|
@@ -1790,9 +1873,9 @@ async function discoverServices(scanPath) {
|
|
|
1790
1873
|
scanPath,
|
|
1791
1874
|
{ maxDepth: parseScanDepth(), ig },
|
|
1792
1875
|
async (dir) => {
|
|
1793
|
-
if (await exists(
|
|
1876
|
+
if (await exists(path7.join(dir, "package.json"))) {
|
|
1794
1877
|
candidateDirs.push(dir);
|
|
1795
|
-
} else if (await exists(
|
|
1878
|
+
} else if (await exists(path7.join(dir, "pyproject.toml")) || await exists(path7.join(dir, "requirements.txt")) || await exists(path7.join(dir, "setup.py"))) {
|
|
1796
1879
|
candidateDirs.push(dir);
|
|
1797
1880
|
}
|
|
1798
1881
|
}
|
|
@@ -1806,8 +1889,8 @@ async function discoverServices(scanPath) {
|
|
|
1806
1889
|
if (!service) continue;
|
|
1807
1890
|
const existingDir = seen.get(service.node.name);
|
|
1808
1891
|
if (existingDir !== void 0) {
|
|
1809
|
-
const a =
|
|
1810
|
-
const b =
|
|
1892
|
+
const a = path7.relative(scanPath, existingDir) || ".";
|
|
1893
|
+
const b = path7.relative(scanPath, dir) || ".";
|
|
1811
1894
|
console.warn(
|
|
1812
1895
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
1813
1896
|
);
|
|
@@ -1816,6 +1899,11 @@ async function discoverServices(scanPath) {
|
|
|
1816
1899
|
seen.set(service.node.name, dir);
|
|
1817
1900
|
out.push(service);
|
|
1818
1901
|
}
|
|
1902
|
+
const codeowners = await loadCodeowners(scanPath);
|
|
1903
|
+
for (const service of out) {
|
|
1904
|
+
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
1905
|
+
if (owner !== void 0) service.node.owner = owner;
|
|
1906
|
+
}
|
|
1819
1907
|
return out;
|
|
1820
1908
|
}
|
|
1821
1909
|
function addServiceNodes(graph, services) {
|
|
@@ -1838,8 +1926,8 @@ function addServiceNodes(graph, services) {
|
|
|
1838
1926
|
}
|
|
1839
1927
|
|
|
1840
1928
|
// src/extract/aliases.ts
|
|
1841
|
-
import
|
|
1842
|
-
import { promises as
|
|
1929
|
+
import path8 from "path";
|
|
1930
|
+
import { promises as fs8 } from "fs";
|
|
1843
1931
|
import { parseAllDocuments } from "yaml";
|
|
1844
1932
|
import { NodeType as NodeType5 } from "@neat.is/types";
|
|
1845
1933
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -1866,21 +1954,29 @@ function indexServicesByName(services) {
|
|
|
1866
1954
|
const map = /* @__PURE__ */ new Map();
|
|
1867
1955
|
for (const s of services) {
|
|
1868
1956
|
map.set(s.node.name, s.node.id);
|
|
1869
|
-
map.set(
|
|
1957
|
+
map.set(path8.basename(s.dir), s.node.id);
|
|
1870
1958
|
}
|
|
1871
1959
|
return map;
|
|
1872
1960
|
}
|
|
1873
1961
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
1874
1962
|
let composePath = null;
|
|
1875
1963
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
1876
|
-
const abs =
|
|
1964
|
+
const abs = path8.join(scanPath, name);
|
|
1877
1965
|
if (await exists(abs)) {
|
|
1878
1966
|
composePath = abs;
|
|
1879
1967
|
break;
|
|
1880
1968
|
}
|
|
1881
1969
|
}
|
|
1882
1970
|
if (!composePath) return;
|
|
1883
|
-
|
|
1971
|
+
let compose;
|
|
1972
|
+
try {
|
|
1973
|
+
compose = await readYaml(composePath);
|
|
1974
|
+
} catch (err) {
|
|
1975
|
+
console.warn(
|
|
1976
|
+
`[neat] aliases compose skipped ${path8.relative(scanPath, composePath)}: ${err.message}`
|
|
1977
|
+
);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1884
1980
|
if (!compose?.services) return;
|
|
1885
1981
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
1886
1982
|
const serviceId3 = serviceIndex.get(composeName);
|
|
@@ -1919,9 +2015,17 @@ function parseDockerfileLabels(content) {
|
|
|
1919
2015
|
}
|
|
1920
2016
|
async function collectDockerfileAliases(graph, services) {
|
|
1921
2017
|
for (const service of services) {
|
|
1922
|
-
const dockerfilePath =
|
|
2018
|
+
const dockerfilePath = path8.join(service.dir, "Dockerfile");
|
|
1923
2019
|
if (!await exists(dockerfilePath)) continue;
|
|
1924
|
-
|
|
2020
|
+
let content;
|
|
2021
|
+
try {
|
|
2022
|
+
content = await fs8.readFile(dockerfilePath, "utf8");
|
|
2023
|
+
} catch (err) {
|
|
2024
|
+
console.warn(
|
|
2025
|
+
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2026
|
+
);
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
1925
2029
|
const aliases = parseDockerfileLabels(content);
|
|
1926
2030
|
if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
|
|
1927
2031
|
}
|
|
@@ -1929,13 +2033,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
1929
2033
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
1930
2034
|
if (depth > max) return [];
|
|
1931
2035
|
const out = [];
|
|
1932
|
-
const entries = await
|
|
2036
|
+
const entries = await fs8.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
1933
2037
|
for (const entry of entries) {
|
|
1934
2038
|
if (entry.isDirectory()) {
|
|
1935
2039
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1936
|
-
out.push(...await walkYamlFiles(
|
|
1937
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
1938
|
-
out.push(
|
|
2040
|
+
out.push(...await walkYamlFiles(path8.join(start, entry.name), depth + 1, max));
|
|
2041
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path8.extname(entry.name))) {
|
|
2042
|
+
out.push(path8.join(start, entry.name));
|
|
1939
2043
|
}
|
|
1940
2044
|
}
|
|
1941
2045
|
return out;
|
|
@@ -1962,7 +2066,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
1962
2066
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
1963
2067
|
const files = await walkYamlFiles(scanPath);
|
|
1964
2068
|
for (const file of files) {
|
|
1965
|
-
const content = await
|
|
2069
|
+
const content = await fs8.readFile(file, "utf8");
|
|
1966
2070
|
let docs;
|
|
1967
2071
|
try {
|
|
1968
2072
|
docs = parseAllDocuments(content).map((d) => d.toJSON());
|
|
@@ -1986,13 +2090,13 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
1986
2090
|
}
|
|
1987
2091
|
|
|
1988
2092
|
// src/extract/databases/index.ts
|
|
1989
|
-
import
|
|
2093
|
+
import path16 from "path";
|
|
1990
2094
|
import { EdgeType as EdgeType3, NodeType as NodeType6, Provenance as Provenance4, databaseId as databaseId2 } from "@neat.is/types";
|
|
1991
2095
|
|
|
1992
2096
|
// src/extract/databases/db-config-yaml.ts
|
|
1993
|
-
import
|
|
2097
|
+
import path9 from "path";
|
|
1994
2098
|
async function parse(serviceDir) {
|
|
1995
|
-
const yamlPath =
|
|
2099
|
+
const yamlPath = path9.join(serviceDir, "db-config.yaml");
|
|
1996
2100
|
if (!await exists(yamlPath)) return [];
|
|
1997
2101
|
const raw = await readYaml(yamlPath);
|
|
1998
2102
|
return [
|
|
@@ -2009,12 +2113,12 @@ async function parse(serviceDir) {
|
|
|
2009
2113
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
2010
2114
|
|
|
2011
2115
|
// src/extract/databases/dotenv.ts
|
|
2012
|
-
import { promises as
|
|
2013
|
-
import
|
|
2116
|
+
import { promises as fs10 } from "fs";
|
|
2117
|
+
import path11 from "path";
|
|
2014
2118
|
|
|
2015
2119
|
// src/extract/databases/shared.ts
|
|
2016
|
-
import { promises as
|
|
2017
|
-
import
|
|
2120
|
+
import { promises as fs9 } from "fs";
|
|
2121
|
+
import path10 from "path";
|
|
2018
2122
|
function schemeToEngine(scheme) {
|
|
2019
2123
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2020
2124
|
switch (s) {
|
|
@@ -2053,14 +2157,14 @@ function parseConnectionString(url) {
|
|
|
2053
2157
|
}
|
|
2054
2158
|
async function readIfExists(filePath) {
|
|
2055
2159
|
try {
|
|
2056
|
-
return await
|
|
2160
|
+
return await fs9.readFile(filePath, "utf8");
|
|
2057
2161
|
} catch {
|
|
2058
2162
|
return null;
|
|
2059
2163
|
}
|
|
2060
2164
|
}
|
|
2061
2165
|
async function findFirst(serviceDir, candidates) {
|
|
2062
2166
|
for (const rel of candidates) {
|
|
2063
|
-
const abs =
|
|
2167
|
+
const abs = path10.join(serviceDir, rel);
|
|
2064
2168
|
const content = await readIfExists(abs);
|
|
2065
2169
|
if (content !== null) return abs;
|
|
2066
2170
|
}
|
|
@@ -2111,15 +2215,15 @@ function parseDotenvLine(line) {
|
|
|
2111
2215
|
return { key, value };
|
|
2112
2216
|
}
|
|
2113
2217
|
async function parse2(serviceDir) {
|
|
2114
|
-
const entries = await
|
|
2218
|
+
const entries = await fs10.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2115
2219
|
const configs = [];
|
|
2116
2220
|
const seen = /* @__PURE__ */ new Set();
|
|
2117
2221
|
for (const entry of entries) {
|
|
2118
2222
|
if (!entry.isFile()) continue;
|
|
2119
2223
|
const match = isConfigFile(entry.name);
|
|
2120
2224
|
if (!match.match || match.fileType !== "env") continue;
|
|
2121
|
-
const filePath =
|
|
2122
|
-
const content = await
|
|
2225
|
+
const filePath = path11.join(serviceDir, entry.name);
|
|
2226
|
+
const content = await fs10.readFile(filePath, "utf8");
|
|
2123
2227
|
for (const line of content.split("\n")) {
|
|
2124
2228
|
const parsed = parseDotenvLine(line);
|
|
2125
2229
|
if (!parsed) continue;
|
|
@@ -2137,9 +2241,9 @@ async function parse2(serviceDir) {
|
|
|
2137
2241
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
2138
2242
|
|
|
2139
2243
|
// src/extract/databases/prisma.ts
|
|
2140
|
-
import
|
|
2244
|
+
import path12 from "path";
|
|
2141
2245
|
async function parse3(serviceDir) {
|
|
2142
|
-
const schemaPath =
|
|
2246
|
+
const schemaPath = path12.join(serviceDir, "prisma", "schema.prisma");
|
|
2143
2247
|
const content = await readIfExists(schemaPath);
|
|
2144
2248
|
if (!content) return [];
|
|
2145
2249
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2268,10 +2372,10 @@ async function parse5(serviceDir) {
|
|
|
2268
2372
|
var knexParser = { name: "knex", parse: parse5 };
|
|
2269
2373
|
|
|
2270
2374
|
// src/extract/databases/ormconfig.ts
|
|
2271
|
-
import
|
|
2375
|
+
import path13 from "path";
|
|
2272
2376
|
async function parse6(serviceDir) {
|
|
2273
2377
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2274
|
-
const abs =
|
|
2378
|
+
const abs = path13.join(serviceDir, candidate);
|
|
2275
2379
|
if (!await exists(abs)) continue;
|
|
2276
2380
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2277
2381
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2329,9 +2433,9 @@ async function parse7(serviceDir) {
|
|
|
2329
2433
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
2330
2434
|
|
|
2331
2435
|
// src/extract/databases/sequelize.ts
|
|
2332
|
-
import
|
|
2436
|
+
import path14 from "path";
|
|
2333
2437
|
async function parse8(serviceDir) {
|
|
2334
|
-
const configPath =
|
|
2438
|
+
const configPath = path14.join(serviceDir, "config", "config.json");
|
|
2335
2439
|
if (!await exists(configPath)) return [];
|
|
2336
2440
|
const raw = await readJson(configPath);
|
|
2337
2441
|
const out = [];
|
|
@@ -2357,7 +2461,7 @@ async function parse8(serviceDir) {
|
|
|
2357
2461
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
2358
2462
|
|
|
2359
2463
|
// src/extract/databases/docker-compose.ts
|
|
2360
|
-
import
|
|
2464
|
+
import path15 from "path";
|
|
2361
2465
|
function portFromService(svc) {
|
|
2362
2466
|
for (const raw of svc.ports ?? []) {
|
|
2363
2467
|
const str = String(raw);
|
|
@@ -2384,7 +2488,7 @@ function databaseFromEnv(svc) {
|
|
|
2384
2488
|
}
|
|
2385
2489
|
async function parse9(serviceDir) {
|
|
2386
2490
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2387
|
-
const abs =
|
|
2491
|
+
const abs = path15.join(serviceDir, name);
|
|
2388
2492
|
if (!await exists(abs)) continue;
|
|
2389
2493
|
const raw = await readYaml(abs);
|
|
2390
2494
|
if (!raw?.services) return [];
|
|
@@ -2564,7 +2668,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2564
2668
|
provenance: Provenance4.EXTRACTED,
|
|
2565
2669
|
...config.sourceFile ? {
|
|
2566
2670
|
evidence: {
|
|
2567
|
-
file:
|
|
2671
|
+
file: path16.relative(scanPath, config.sourceFile).split(path16.sep).join("/")
|
|
2568
2672
|
}
|
|
2569
2673
|
} : {}
|
|
2570
2674
|
};
|
|
@@ -2591,15 +2695,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2591
2695
|
}
|
|
2592
2696
|
|
|
2593
2697
|
// src/extract/configs.ts
|
|
2594
|
-
import { promises as
|
|
2595
|
-
import
|
|
2698
|
+
import { promises as fs11 } from "fs";
|
|
2699
|
+
import path17 from "path";
|
|
2596
2700
|
import { EdgeType as EdgeType4, NodeType as NodeType7, Provenance as Provenance5, configId } from "@neat.is/types";
|
|
2597
2701
|
async function walkConfigFiles(dir) {
|
|
2598
2702
|
const out = [];
|
|
2599
2703
|
async function walk(current) {
|
|
2600
|
-
const entries = await
|
|
2704
|
+
const entries = await fs11.readdir(current, { withFileTypes: true });
|
|
2601
2705
|
for (const entry of entries) {
|
|
2602
|
-
const full =
|
|
2706
|
+
const full = path17.join(current, entry.name);
|
|
2603
2707
|
if (entry.isDirectory()) {
|
|
2604
2708
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2605
2709
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -2616,13 +2720,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2616
2720
|
for (const service of services) {
|
|
2617
2721
|
const configFiles = await walkConfigFiles(service.dir);
|
|
2618
2722
|
for (const file of configFiles) {
|
|
2619
|
-
const relPath =
|
|
2723
|
+
const relPath = path17.relative(scanPath, file);
|
|
2620
2724
|
const node = {
|
|
2621
2725
|
id: configId(relPath),
|
|
2622
2726
|
type: NodeType7.ConfigNode,
|
|
2623
|
-
name:
|
|
2727
|
+
name: path17.basename(file),
|
|
2624
2728
|
path: relPath,
|
|
2625
|
-
fileType: isConfigFile(
|
|
2729
|
+
fileType: isConfigFile(path17.basename(file)).fileType
|
|
2626
2730
|
};
|
|
2627
2731
|
if (!graph.hasNode(node.id)) {
|
|
2628
2732
|
graph.addNode(node.id, node);
|
|
@@ -2634,7 +2738,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2634
2738
|
target: node.id,
|
|
2635
2739
|
type: EdgeType4.CONFIGURED_BY,
|
|
2636
2740
|
provenance: Provenance5.EXTRACTED,
|
|
2637
|
-
evidence: { file: relPath.split(
|
|
2741
|
+
evidence: { file: relPath.split(path17.sep).join("/") }
|
|
2638
2742
|
};
|
|
2639
2743
|
if (!graph.hasEdge(edge.id)) {
|
|
2640
2744
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2649,24 +2753,24 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2649
2753
|
import { EdgeType as EdgeType6, NodeType as NodeType8, Provenance as Provenance7 } from "@neat.is/types";
|
|
2650
2754
|
|
|
2651
2755
|
// src/extract/calls/http.ts
|
|
2652
|
-
import
|
|
2756
|
+
import path19 from "path";
|
|
2653
2757
|
import Parser from "tree-sitter";
|
|
2654
2758
|
import JavaScript from "tree-sitter-javascript";
|
|
2655
2759
|
import Python from "tree-sitter-python";
|
|
2656
2760
|
import { EdgeType as EdgeType5, Provenance as Provenance6 } from "@neat.is/types";
|
|
2657
2761
|
|
|
2658
2762
|
// src/extract/calls/shared.ts
|
|
2659
|
-
import { promises as
|
|
2660
|
-
import
|
|
2763
|
+
import { promises as fs12 } from "fs";
|
|
2764
|
+
import path18 from "path";
|
|
2661
2765
|
async function walkSourceFiles(dir) {
|
|
2662
2766
|
const out = [];
|
|
2663
2767
|
async function walk(current) {
|
|
2664
|
-
const entries = await
|
|
2768
|
+
const entries = await fs12.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2665
2769
|
for (const entry of entries) {
|
|
2666
|
-
const full =
|
|
2770
|
+
const full = path18.join(current, entry.name);
|
|
2667
2771
|
if (entry.isDirectory()) {
|
|
2668
2772
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2669
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
2773
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path18.extname(entry.name))) {
|
|
2670
2774
|
out.push(full);
|
|
2671
2775
|
}
|
|
2672
2776
|
}
|
|
@@ -2679,7 +2783,7 @@ async function loadSourceFiles(dir) {
|
|
|
2679
2783
|
const out = [];
|
|
2680
2784
|
for (const p of paths) {
|
|
2681
2785
|
try {
|
|
2682
|
-
const content = await
|
|
2786
|
+
const content = await fs12.readFile(p, "utf8");
|
|
2683
2787
|
out.push({ path: p, content });
|
|
2684
2788
|
} catch {
|
|
2685
2789
|
}
|
|
@@ -2735,9 +2839,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2735
2839
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
2736
2840
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
2737
2841
|
for (const service of services) {
|
|
2738
|
-
knownHosts.add(
|
|
2842
|
+
knownHosts.add(path19.basename(service.dir));
|
|
2739
2843
|
knownHosts.add(service.pkg.name);
|
|
2740
|
-
hostToNodeId.set(
|
|
2844
|
+
hostToNodeId.set(path19.basename(service.dir), service.node.id);
|
|
2741
2845
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
2742
2846
|
}
|
|
2743
2847
|
let edgesAdded = 0;
|
|
@@ -2745,7 +2849,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2745
2849
|
const files = await loadSourceFiles(service.dir);
|
|
2746
2850
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
2747
2851
|
for (const file of files) {
|
|
2748
|
-
const parser =
|
|
2852
|
+
const parser = path19.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
2749
2853
|
let targets;
|
|
2750
2854
|
try {
|
|
2751
2855
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -2773,7 +2877,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2773
2877
|
type: EdgeType5.CALLS,
|
|
2774
2878
|
provenance: Provenance6.EXTRACTED,
|
|
2775
2879
|
evidence: {
|
|
2776
|
-
file:
|
|
2880
|
+
file: path19.relative(service.dir, evidenceFile.file),
|
|
2777
2881
|
line,
|
|
2778
2882
|
snippet: snippet(fileContent, line)
|
|
2779
2883
|
}
|
|
@@ -2788,7 +2892,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2788
2892
|
}
|
|
2789
2893
|
|
|
2790
2894
|
// src/extract/calls/kafka.ts
|
|
2791
|
-
import
|
|
2895
|
+
import path20 from "path";
|
|
2792
2896
|
import { infraId } from "@neat.is/types";
|
|
2793
2897
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
2794
2898
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -2815,7 +2919,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
2815
2919
|
kind: "kafka-topic",
|
|
2816
2920
|
edgeType,
|
|
2817
2921
|
evidence: {
|
|
2818
|
-
file:
|
|
2922
|
+
file: path20.relative(serviceDir, file.path),
|
|
2819
2923
|
line,
|
|
2820
2924
|
snippet: snippet(file.content, line)
|
|
2821
2925
|
}
|
|
@@ -2827,7 +2931,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
2827
2931
|
}
|
|
2828
2932
|
|
|
2829
2933
|
// src/extract/calls/redis.ts
|
|
2830
|
-
import
|
|
2934
|
+
import path21 from "path";
|
|
2831
2935
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
2832
2936
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
2833
2937
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -2846,7 +2950,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
2846
2950
|
kind: "redis",
|
|
2847
2951
|
edgeType: "CALLS",
|
|
2848
2952
|
evidence: {
|
|
2849
|
-
file:
|
|
2953
|
+
file: path21.relative(serviceDir, file.path),
|
|
2850
2954
|
line,
|
|
2851
2955
|
snippet: snippet(file.content, line)
|
|
2852
2956
|
}
|
|
@@ -2856,7 +2960,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
2856
2960
|
}
|
|
2857
2961
|
|
|
2858
2962
|
// src/extract/calls/aws.ts
|
|
2859
|
-
import
|
|
2963
|
+
import path22 from "path";
|
|
2860
2964
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
2861
2965
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
2862
2966
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -2886,7 +2990,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
2886
2990
|
kind,
|
|
2887
2991
|
edgeType: "CALLS",
|
|
2888
2992
|
evidence: {
|
|
2889
|
-
file:
|
|
2993
|
+
file: path22.relative(serviceDir, file.path),
|
|
2890
2994
|
line,
|
|
2891
2995
|
snippet: snippet(file.content, line)
|
|
2892
2996
|
}
|
|
@@ -2910,7 +3014,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
2910
3014
|
}
|
|
2911
3015
|
|
|
2912
3016
|
// src/extract/calls/grpc.ts
|
|
2913
|
-
import
|
|
3017
|
+
import path23 from "path";
|
|
2914
3018
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
2915
3019
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
2916
3020
|
function isLikelyAddress(value) {
|
|
@@ -2935,7 +3039,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
2935
3039
|
kind: "grpc-service",
|
|
2936
3040
|
edgeType: "CALLS",
|
|
2937
3041
|
evidence: {
|
|
2938
|
-
file:
|
|
3042
|
+
file: path23.relative(serviceDir, file.path),
|
|
2939
3043
|
line,
|
|
2940
3044
|
snippet: snippet(file.content, line)
|
|
2941
3045
|
}
|
|
@@ -3011,7 +3115,7 @@ async function addCallEdges(graph, services) {
|
|
|
3011
3115
|
}
|
|
3012
3116
|
|
|
3013
3117
|
// src/extract/infra/docker-compose.ts
|
|
3014
|
-
import
|
|
3118
|
+
import path24 from "path";
|
|
3015
3119
|
import { EdgeType as EdgeType7, Provenance as Provenance8 } from "@neat.is/types";
|
|
3016
3120
|
|
|
3017
3121
|
// src/extract/infra/shared.ts
|
|
@@ -3048,7 +3152,7 @@ function dependsOnList(value) {
|
|
|
3048
3152
|
}
|
|
3049
3153
|
function serviceNameToServiceNode(name, services) {
|
|
3050
3154
|
for (const s of services) {
|
|
3051
|
-
if (s.node.name === name ||
|
|
3155
|
+
if (s.node.name === name || path24.basename(s.dir) === name) return s.node.id;
|
|
3052
3156
|
}
|
|
3053
3157
|
return null;
|
|
3054
3158
|
}
|
|
@@ -3057,16 +3161,24 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3057
3161
|
let edgesAdded = 0;
|
|
3058
3162
|
let composePath = null;
|
|
3059
3163
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3060
|
-
const abs =
|
|
3164
|
+
const abs = path24.join(scanPath, name);
|
|
3061
3165
|
if (await exists(abs)) {
|
|
3062
3166
|
composePath = abs;
|
|
3063
3167
|
break;
|
|
3064
3168
|
}
|
|
3065
3169
|
}
|
|
3066
3170
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
3067
|
-
|
|
3171
|
+
let compose;
|
|
3172
|
+
try {
|
|
3173
|
+
compose = await readYaml(composePath);
|
|
3174
|
+
} catch (err) {
|
|
3175
|
+
console.warn(
|
|
3176
|
+
`[neat] infra docker-compose skipped ${path24.relative(scanPath, composePath)}: ${err.message}`
|
|
3177
|
+
);
|
|
3178
|
+
return { nodesAdded, edgesAdded };
|
|
3179
|
+
}
|
|
3068
3180
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3069
|
-
const evidenceFile =
|
|
3181
|
+
const evidenceFile = path24.relative(scanPath, composePath).split(path24.sep).join("/");
|
|
3070
3182
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3071
3183
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3072
3184
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3106,8 +3218,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3106
3218
|
}
|
|
3107
3219
|
|
|
3108
3220
|
// src/extract/infra/dockerfile.ts
|
|
3109
|
-
import
|
|
3110
|
-
import { promises as
|
|
3221
|
+
import path25 from "path";
|
|
3222
|
+
import { promises as fs13 } from "fs";
|
|
3111
3223
|
import { EdgeType as EdgeType8, Provenance as Provenance9 } from "@neat.is/types";
|
|
3112
3224
|
function runtimeImage(content) {
|
|
3113
3225
|
const lines = content.split("\n");
|
|
@@ -3127,9 +3239,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3127
3239
|
let nodesAdded = 0;
|
|
3128
3240
|
let edgesAdded = 0;
|
|
3129
3241
|
for (const service of services) {
|
|
3130
|
-
const dockerfilePath =
|
|
3242
|
+
const dockerfilePath = path25.join(service.dir, "Dockerfile");
|
|
3131
3243
|
if (!await exists(dockerfilePath)) continue;
|
|
3132
|
-
|
|
3244
|
+
let content;
|
|
3245
|
+
try {
|
|
3246
|
+
content = await fs13.readFile(dockerfilePath, "utf8");
|
|
3247
|
+
} catch (err) {
|
|
3248
|
+
console.warn(
|
|
3249
|
+
`[neat] infra dockerfile skipped ${path25.relative(scanPath, dockerfilePath)}: ${err.message}`
|
|
3250
|
+
);
|
|
3251
|
+
continue;
|
|
3252
|
+
}
|
|
3133
3253
|
const image = runtimeImage(content);
|
|
3134
3254
|
if (!image) continue;
|
|
3135
3255
|
const node = makeInfraNode("container-image", image);
|
|
@@ -3146,7 +3266,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3146
3266
|
type: EdgeType8.RUNS_ON,
|
|
3147
3267
|
provenance: Provenance9.EXTRACTED,
|
|
3148
3268
|
evidence: {
|
|
3149
|
-
file:
|
|
3269
|
+
file: path25.relative(scanPath, dockerfilePath).split(path25.sep).join("/")
|
|
3150
3270
|
}
|
|
3151
3271
|
};
|
|
3152
3272
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3157,19 +3277,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3157
3277
|
}
|
|
3158
3278
|
|
|
3159
3279
|
// src/extract/infra/terraform.ts
|
|
3160
|
-
import { promises as
|
|
3161
|
-
import
|
|
3280
|
+
import { promises as fs14 } from "fs";
|
|
3281
|
+
import path26 from "path";
|
|
3162
3282
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3163
3283
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3164
3284
|
if (depth > max) return [];
|
|
3165
3285
|
const out = [];
|
|
3166
|
-
const entries = await
|
|
3286
|
+
const entries = await fs14.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3167
3287
|
for (const entry of entries) {
|
|
3168
3288
|
if (entry.isDirectory()) {
|
|
3169
3289
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3170
|
-
out.push(...await walkTfFiles(
|
|
3290
|
+
out.push(...await walkTfFiles(path26.join(start, entry.name), depth + 1, max));
|
|
3171
3291
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3172
|
-
out.push(
|
|
3292
|
+
out.push(path26.join(start, entry.name));
|
|
3173
3293
|
}
|
|
3174
3294
|
}
|
|
3175
3295
|
return out;
|
|
@@ -3178,7 +3298,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3178
3298
|
let nodesAdded = 0;
|
|
3179
3299
|
const files = await walkTfFiles(scanPath);
|
|
3180
3300
|
for (const file of files) {
|
|
3181
|
-
const content = await
|
|
3301
|
+
const content = await fs14.readFile(file, "utf8");
|
|
3182
3302
|
RESOURCE_RE.lastIndex = 0;
|
|
3183
3303
|
let m;
|
|
3184
3304
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3195,8 +3315,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3195
3315
|
}
|
|
3196
3316
|
|
|
3197
3317
|
// src/extract/infra/k8s.ts
|
|
3198
|
-
import { promises as
|
|
3199
|
-
import
|
|
3318
|
+
import { promises as fs15 } from "fs";
|
|
3319
|
+
import path27 from "path";
|
|
3200
3320
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
3201
3321
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3202
3322
|
Service: "k8s-service",
|
|
@@ -3210,13 +3330,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3210
3330
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3211
3331
|
if (depth > max) return [];
|
|
3212
3332
|
const out = [];
|
|
3213
|
-
const entries = await
|
|
3333
|
+
const entries = await fs15.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3214
3334
|
for (const entry of entries) {
|
|
3215
3335
|
if (entry.isDirectory()) {
|
|
3216
3336
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3217
|
-
out.push(...await walkYamlFiles2(
|
|
3218
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3219
|
-
out.push(
|
|
3337
|
+
out.push(...await walkYamlFiles2(path27.join(start, entry.name), depth + 1, max));
|
|
3338
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path27.extname(entry.name))) {
|
|
3339
|
+
out.push(path27.join(start, entry.name));
|
|
3220
3340
|
}
|
|
3221
3341
|
}
|
|
3222
3342
|
return out;
|
|
@@ -3225,7 +3345,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3225
3345
|
let nodesAdded = 0;
|
|
3226
3346
|
const files = await walkYamlFiles2(scanPath);
|
|
3227
3347
|
for (const file of files) {
|
|
3228
|
-
const content = await
|
|
3348
|
+
const content = await fs15.readFile(file, "utf8");
|
|
3229
3349
|
let docs;
|
|
3230
3350
|
try {
|
|
3231
3351
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -3290,8 +3410,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3290
3410
|
}
|
|
3291
3411
|
|
|
3292
3412
|
// src/persist.ts
|
|
3293
|
-
import { promises as
|
|
3294
|
-
import
|
|
3413
|
+
import { promises as fs16 } from "fs";
|
|
3414
|
+
import path28 from "path";
|
|
3295
3415
|
var SCHEMA_VERSION = 2;
|
|
3296
3416
|
function migrateV1ToV2(payload) {
|
|
3297
3417
|
const nodes = payload.graph.nodes;
|
|
@@ -3305,7 +3425,7 @@ function migrateV1ToV2(payload) {
|
|
|
3305
3425
|
return { ...payload, schemaVersion: 2 };
|
|
3306
3426
|
}
|
|
3307
3427
|
async function ensureDir(filePath) {
|
|
3308
|
-
await
|
|
3428
|
+
await fs16.mkdir(path28.dirname(filePath), { recursive: true });
|
|
3309
3429
|
}
|
|
3310
3430
|
async function saveGraphToDisk(graph, outPath) {
|
|
3311
3431
|
await ensureDir(outPath);
|
|
@@ -3315,13 +3435,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
3315
3435
|
graph: graph.export()
|
|
3316
3436
|
};
|
|
3317
3437
|
const tmp = `${outPath}.tmp`;
|
|
3318
|
-
await
|
|
3319
|
-
await
|
|
3438
|
+
await fs16.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
3439
|
+
await fs16.rename(tmp, outPath);
|
|
3320
3440
|
}
|
|
3321
3441
|
async function loadGraphFromDisk(graph, outPath) {
|
|
3322
3442
|
let raw;
|
|
3323
3443
|
try {
|
|
3324
|
-
raw = await
|
|
3444
|
+
raw = await fs16.readFile(outPath, "utf8");
|
|
3325
3445
|
} catch (err) {
|
|
3326
3446
|
if (err.code === "ENOENT") return;
|
|
3327
3447
|
throw err;
|
|
@@ -3373,23 +3493,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
3373
3493
|
}
|
|
3374
3494
|
|
|
3375
3495
|
// src/projects.ts
|
|
3376
|
-
import
|
|
3496
|
+
import path29 from "path";
|
|
3377
3497
|
function pathsForProject(project, baseDir) {
|
|
3378
3498
|
if (project === DEFAULT_PROJECT) {
|
|
3379
3499
|
return {
|
|
3380
|
-
snapshotPath:
|
|
3381
|
-
errorsPath:
|
|
3382
|
-
staleEventsPath:
|
|
3383
|
-
embeddingsCachePath:
|
|
3384
|
-
policyViolationsPath:
|
|
3500
|
+
snapshotPath: path29.join(baseDir, "graph.json"),
|
|
3501
|
+
errorsPath: path29.join(baseDir, "errors.ndjson"),
|
|
3502
|
+
staleEventsPath: path29.join(baseDir, "stale-events.ndjson"),
|
|
3503
|
+
embeddingsCachePath: path29.join(baseDir, "embeddings.json"),
|
|
3504
|
+
policyViolationsPath: path29.join(baseDir, "policy-violations.ndjson")
|
|
3385
3505
|
};
|
|
3386
3506
|
}
|
|
3387
3507
|
return {
|
|
3388
|
-
snapshotPath:
|
|
3389
|
-
errorsPath:
|
|
3390
|
-
staleEventsPath:
|
|
3391
|
-
embeddingsCachePath:
|
|
3392
|
-
policyViolationsPath:
|
|
3508
|
+
snapshotPath: path29.join(baseDir, `${project}.json`),
|
|
3509
|
+
errorsPath: path29.join(baseDir, `errors.${project}.ndjson`),
|
|
3510
|
+
staleEventsPath: path29.join(baseDir, `stale-events.${project}.ndjson`),
|
|
3511
|
+
embeddingsCachePath: path29.join(baseDir, `embeddings.${project}.json`),
|
|
3512
|
+
policyViolationsPath: path29.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3393
3513
|
};
|
|
3394
3514
|
}
|
|
3395
3515
|
var Projects = class {
|
|
@@ -3428,9 +3548,9 @@ function parseExtraProjects(raw) {
|
|
|
3428
3548
|
}
|
|
3429
3549
|
|
|
3430
3550
|
// src/registry.ts
|
|
3431
|
-
import { promises as
|
|
3551
|
+
import { promises as fs17 } from "fs";
|
|
3432
3552
|
import os2 from "os";
|
|
3433
|
-
import
|
|
3553
|
+
import path30 from "path";
|
|
3434
3554
|
import {
|
|
3435
3555
|
RegistryFileSchema
|
|
3436
3556
|
} from "@neat.is/types";
|
|
@@ -3438,41 +3558,41 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
3438
3558
|
var LOCK_RETRY_MS = 50;
|
|
3439
3559
|
function neatHome() {
|
|
3440
3560
|
const override = process.env.NEAT_HOME;
|
|
3441
|
-
if (override && override.length > 0) return
|
|
3442
|
-
return
|
|
3561
|
+
if (override && override.length > 0) return path30.resolve(override);
|
|
3562
|
+
return path30.join(os2.homedir(), ".neat");
|
|
3443
3563
|
}
|
|
3444
3564
|
function registryPath() {
|
|
3445
|
-
return
|
|
3565
|
+
return path30.join(neatHome(), "projects.json");
|
|
3446
3566
|
}
|
|
3447
3567
|
function registryLockPath() {
|
|
3448
|
-
return
|
|
3568
|
+
return path30.join(neatHome(), "projects.json.lock");
|
|
3449
3569
|
}
|
|
3450
3570
|
async function normalizeProjectPath(input) {
|
|
3451
|
-
const resolved =
|
|
3571
|
+
const resolved = path30.resolve(input);
|
|
3452
3572
|
try {
|
|
3453
|
-
return await
|
|
3573
|
+
return await fs17.realpath(resolved);
|
|
3454
3574
|
} catch {
|
|
3455
3575
|
return resolved;
|
|
3456
3576
|
}
|
|
3457
3577
|
}
|
|
3458
3578
|
async function writeAtomically(target, contents) {
|
|
3459
|
-
await
|
|
3579
|
+
await fs17.mkdir(path30.dirname(target), { recursive: true });
|
|
3460
3580
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
3461
|
-
const fd = await
|
|
3581
|
+
const fd = await fs17.open(tmp, "w");
|
|
3462
3582
|
try {
|
|
3463
3583
|
await fd.writeFile(contents, "utf8");
|
|
3464
3584
|
await fd.sync();
|
|
3465
3585
|
} finally {
|
|
3466
3586
|
await fd.close();
|
|
3467
3587
|
}
|
|
3468
|
-
await
|
|
3588
|
+
await fs17.rename(tmp, target);
|
|
3469
3589
|
}
|
|
3470
3590
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
3471
3591
|
const deadline = Date.now() + timeoutMs;
|
|
3472
|
-
await
|
|
3592
|
+
await fs17.mkdir(path30.dirname(lockPath), { recursive: true });
|
|
3473
3593
|
while (true) {
|
|
3474
3594
|
try {
|
|
3475
|
-
const fd = await
|
|
3595
|
+
const fd = await fs17.open(lockPath, "wx");
|
|
3476
3596
|
await fd.close();
|
|
3477
3597
|
return;
|
|
3478
3598
|
} catch (err) {
|
|
@@ -3488,7 +3608,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
3488
3608
|
}
|
|
3489
3609
|
}
|
|
3490
3610
|
async function releaseLock(lockPath) {
|
|
3491
|
-
await
|
|
3611
|
+
await fs17.unlink(lockPath).catch(() => {
|
|
3492
3612
|
});
|
|
3493
3613
|
}
|
|
3494
3614
|
async function withLock(fn) {
|
|
@@ -3504,7 +3624,7 @@ async function readRegistry() {
|
|
|
3504
3624
|
const file = registryPath();
|
|
3505
3625
|
let raw;
|
|
3506
3626
|
try {
|
|
3507
|
-
raw = await
|
|
3627
|
+
raw = await fs17.readFile(file, "utf8");
|
|
3508
3628
|
} catch (err) {
|
|
3509
3629
|
if (err.code === "ENOENT") {
|
|
3510
3630
|
return { version: 1, projects: [] };
|
|
@@ -3653,4 +3773,4 @@ export {
|
|
|
3653
3773
|
touchLastSeen,
|
|
3654
3774
|
removeProject
|
|
3655
3775
|
};
|
|
3656
|
-
//# sourceMappingURL=chunk-
|
|
3776
|
+
//# sourceMappingURL=chunk-IRPH6KL4.js.map
|