@neat.is/core 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-66Z5IEFY.js → chunk-ICFH326H.js} +2 -2
- package/dist/{chunk-FIXKIYNF.js → chunk-VABXPLDT.js} +457 -206
- package/dist/chunk-VABXPLDT.js.map +1 -0
- package/dist/cli.cjs +555 -309
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +24 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +451 -221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +458 -228
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +420 -190
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-FIXKIYNF.js.map +0 -1
- /package/dist/{chunk-66Z5IEFY.js.map → chunk-ICFH326H.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function resolveProtoRoot() {
|
|
103
|
-
const here =
|
|
104
|
-
return
|
|
103
|
+
const here = import_node_path33.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
104
|
+
return import_node_path33.default.resolve(here, "..", "proto");
|
|
105
105
|
}
|
|
106
106
|
function loadTraceService() {
|
|
107
107
|
const protoRoot = resolveProtoRoot();
|
|
@@ -156,13 +156,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
156
156
|
})
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
var import_node_url,
|
|
159
|
+
var import_node_url, import_node_path33, grpc, protoLoader;
|
|
160
160
|
var init_otel_grpc = __esm({
|
|
161
161
|
"src/otel-grpc.ts"() {
|
|
162
162
|
"use strict";
|
|
163
163
|
init_cjs_shims();
|
|
164
164
|
import_node_url = require("url");
|
|
165
|
-
|
|
165
|
+
import_node_path33 = __toESM(require("path"), 1);
|
|
166
166
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
167
167
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
168
168
|
init_otel();
|
|
@@ -259,10 +259,10 @@ function parseOtlpRequest(body) {
|
|
|
259
259
|
}
|
|
260
260
|
function loadProtobufDecoder() {
|
|
261
261
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
262
|
-
const here =
|
|
263
|
-
const protoRoot =
|
|
262
|
+
const here = import_node_path34.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
263
|
+
const protoRoot = import_node_path34.default.resolve(here, "..", "proto");
|
|
264
264
|
const root = new import_protobufjs.default.Root();
|
|
265
|
-
root.resolvePath = (_origin, target) =>
|
|
265
|
+
root.resolvePath = (_origin, target) => import_node_path34.default.resolve(protoRoot, target);
|
|
266
266
|
root.loadSync(
|
|
267
267
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
268
268
|
{ keepCase: true }
|
|
@@ -354,12 +354,12 @@ async function buildOtelReceiver(opts) {
|
|
|
354
354
|
};
|
|
355
355
|
return decorated;
|
|
356
356
|
}
|
|
357
|
-
var
|
|
357
|
+
var import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
358
358
|
var init_otel = __esm({
|
|
359
359
|
"src/otel.ts"() {
|
|
360
360
|
"use strict";
|
|
361
361
|
init_cjs_shims();
|
|
362
|
-
|
|
362
|
+
import_node_path34 = __toESM(require("path"), 1);
|
|
363
363
|
import_node_url2 = require("url");
|
|
364
364
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
365
365
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -369,13 +369,13 @@ var init_otel = __esm({
|
|
|
369
369
|
|
|
370
370
|
// src/neatd.ts
|
|
371
371
|
init_cjs_shims();
|
|
372
|
-
var
|
|
373
|
-
var
|
|
372
|
+
var import_node_fs22 = require("fs");
|
|
373
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
374
374
|
|
|
375
375
|
// src/daemon.ts
|
|
376
376
|
init_cjs_shims();
|
|
377
|
-
var
|
|
378
|
-
var
|
|
377
|
+
var import_node_fs21 = require("fs");
|
|
378
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
379
379
|
|
|
380
380
|
// src/graph.ts
|
|
381
381
|
init_cjs_shims();
|
|
@@ -831,19 +831,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
831
831
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
832
832
|
let best = { path: [start], edges: [] };
|
|
833
833
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
834
|
-
function step(node,
|
|
835
|
-
if (
|
|
836
|
-
best = { path: [...
|
|
834
|
+
function step(node, path38, edges) {
|
|
835
|
+
if (path38.length > best.path.length) {
|
|
836
|
+
best = { path: [...path38], edges: [...edges] };
|
|
837
837
|
}
|
|
838
|
-
if (
|
|
838
|
+
if (path38.length - 1 >= maxDepth) return;
|
|
839
839
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
840
840
|
for (const [srcId, edge] of incoming) {
|
|
841
841
|
if (visited.has(srcId)) continue;
|
|
842
842
|
visited.add(srcId);
|
|
843
|
-
|
|
843
|
+
path38.push(srcId);
|
|
844
844
|
edges.push(edge);
|
|
845
|
-
step(srcId,
|
|
846
|
-
|
|
845
|
+
step(srcId, path38, edges);
|
|
846
|
+
path38.pop();
|
|
847
847
|
edges.pop();
|
|
848
848
|
visited.delete(srcId);
|
|
849
849
|
}
|
|
@@ -1785,8 +1785,8 @@ async function readErrorEvents(errorsPath) {
|
|
|
1785
1785
|
|
|
1786
1786
|
// src/extract/services.ts
|
|
1787
1787
|
init_cjs_shims();
|
|
1788
|
-
var
|
|
1789
|
-
var
|
|
1788
|
+
var import_node_fs8 = require("fs");
|
|
1789
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1790
1790
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1791
1791
|
var import_minimatch2 = require("minimatch");
|
|
1792
1792
|
var import_types5 = require("@neat.is/types");
|
|
@@ -1810,9 +1810,107 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1810
1810
|
function isConfigFile(name) {
|
|
1811
1811
|
const ext = import_node_path4.default.extname(name);
|
|
1812
1812
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
1813
|
-
if (name === ".env" || name.startsWith(".env."))
|
|
1813
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
1814
|
+
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
1815
|
+
return { match: true, fileType: "env" };
|
|
1816
|
+
}
|
|
1814
1817
|
return { match: false, fileType: "" };
|
|
1815
1818
|
}
|
|
1819
|
+
function isTestPath(filePath) {
|
|
1820
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
1821
|
+
const segments = normalised.split("/");
|
|
1822
|
+
for (const seg of segments) {
|
|
1823
|
+
if (seg === "__tests__" || seg === "__fixtures__" || seg === "integration-tests") {
|
|
1824
|
+
return true;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
const base = segments[segments.length - 1] ?? "";
|
|
1828
|
+
return /\.(spec|test)\.(?:tsx?|jsx?|mjs|cjs|py)$/i.test(base);
|
|
1829
|
+
}
|
|
1830
|
+
function isEnvTemplateFile(name) {
|
|
1831
|
+
if (name === ".env.template" || name === ".env.example" || name === ".env.sample") {
|
|
1832
|
+
return true;
|
|
1833
|
+
}
|
|
1834
|
+
return /^\.env\.[^.]+\.(?:template|example|sample)$/i.test(name);
|
|
1835
|
+
}
|
|
1836
|
+
function maskCommentsInSource(src) {
|
|
1837
|
+
const len = src.length;
|
|
1838
|
+
const out = new Array(len);
|
|
1839
|
+
let i = 0;
|
|
1840
|
+
let inString = 0;
|
|
1841
|
+
let escaped = false;
|
|
1842
|
+
while (i < len) {
|
|
1843
|
+
const c = src[i];
|
|
1844
|
+
if (inString !== 0) {
|
|
1845
|
+
out[i] = c;
|
|
1846
|
+
if (escaped) {
|
|
1847
|
+
escaped = false;
|
|
1848
|
+
} else if (c === "\\") {
|
|
1849
|
+
escaped = true;
|
|
1850
|
+
} else if (c === inString) {
|
|
1851
|
+
inString = 0;
|
|
1852
|
+
}
|
|
1853
|
+
i++;
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
if (c === "/" && i + 1 < len) {
|
|
1857
|
+
const next = src[i + 1];
|
|
1858
|
+
if (next === "/") {
|
|
1859
|
+
out[i] = " ";
|
|
1860
|
+
out[i + 1] = " ";
|
|
1861
|
+
let j = i + 2;
|
|
1862
|
+
while (j < len && src[j] !== "\n") {
|
|
1863
|
+
out[j] = " ";
|
|
1864
|
+
j++;
|
|
1865
|
+
}
|
|
1866
|
+
i = j;
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (next === "*") {
|
|
1870
|
+
out[i] = " ";
|
|
1871
|
+
out[i + 1] = " ";
|
|
1872
|
+
let j = i + 2;
|
|
1873
|
+
while (j < len) {
|
|
1874
|
+
if (src[j] === "\n") {
|
|
1875
|
+
out[j] = "\n";
|
|
1876
|
+
j++;
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
if (src[j] === "*" && j + 1 < len && src[j + 1] === "/") {
|
|
1880
|
+
out[j] = " ";
|
|
1881
|
+
out[j + 1] = " ";
|
|
1882
|
+
j += 2;
|
|
1883
|
+
break;
|
|
1884
|
+
}
|
|
1885
|
+
out[j] = " ";
|
|
1886
|
+
j++;
|
|
1887
|
+
}
|
|
1888
|
+
i = j;
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
out[i] = c;
|
|
1893
|
+
if (c === "'" || c === '"' || c === "`") inString = c;
|
|
1894
|
+
i++;
|
|
1895
|
+
}
|
|
1896
|
+
return out.join("");
|
|
1897
|
+
}
|
|
1898
|
+
var URL_LIKE = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
1899
|
+
function urlMatchesHost(urlString, host) {
|
|
1900
|
+
if (typeof urlString !== "string" || urlString.length === 0) return false;
|
|
1901
|
+
if (!URL_LIKE.test(urlString)) return false;
|
|
1902
|
+
const [wantedHost, wantedPort] = host.split(":");
|
|
1903
|
+
let parsed;
|
|
1904
|
+
try {
|
|
1905
|
+
const candidate = urlString.startsWith("//") ? `http:${urlString}` : urlString;
|
|
1906
|
+
parsed = new URL(candidate);
|
|
1907
|
+
} catch {
|
|
1908
|
+
return false;
|
|
1909
|
+
}
|
|
1910
|
+
if (parsed.hostname.toLowerCase() !== (wantedHost ?? "").toLowerCase()) return false;
|
|
1911
|
+
if (wantedPort && parsed.port !== wantedPort) return false;
|
|
1912
|
+
return true;
|
|
1913
|
+
}
|
|
1816
1914
|
function cleanVersion(raw) {
|
|
1817
1915
|
if (!raw) return void 0;
|
|
1818
1916
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
@@ -1968,6 +2066,33 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
1968
2066
|
return author ?? void 0;
|
|
1969
2067
|
}
|
|
1970
2068
|
|
|
2069
|
+
// src/extract/errors.ts
|
|
2070
|
+
init_cjs_shims();
|
|
2071
|
+
var import_node_fs7 = require("fs");
|
|
2072
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2073
|
+
var sink = [];
|
|
2074
|
+
function recordExtractionError(producer, file, err) {
|
|
2075
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
2076
|
+
sink.push({
|
|
2077
|
+
producer,
|
|
2078
|
+
file,
|
|
2079
|
+
error: e.message,
|
|
2080
|
+
stack: e.stack,
|
|
2081
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2082
|
+
source: "extract"
|
|
2083
|
+
});
|
|
2084
|
+
console.warn(`[neat] ${producer} skipped ${file}: ${e.message}`);
|
|
2085
|
+
}
|
|
2086
|
+
function drainExtractionErrors() {
|
|
2087
|
+
return sink.splice(0, sink.length);
|
|
2088
|
+
}
|
|
2089
|
+
async function writeExtractionErrors(errors, errorsPath) {
|
|
2090
|
+
if (errors.length === 0) return;
|
|
2091
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(errorsPath), { recursive: true });
|
|
2092
|
+
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2093
|
+
await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
|
|
2094
|
+
}
|
|
2095
|
+
|
|
1971
2096
|
// src/extract/services.ts
|
|
1972
2097
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
1973
2098
|
function parseScanDepth() {
|
|
@@ -1984,21 +2109,21 @@ function workspaceGlobs(pkg) {
|
|
|
1984
2109
|
return null;
|
|
1985
2110
|
}
|
|
1986
2111
|
async function loadGitignore(scanPath) {
|
|
1987
|
-
const gitignorePath =
|
|
2112
|
+
const gitignorePath = import_node_path8.default.join(scanPath, ".gitignore");
|
|
1988
2113
|
if (!await exists(gitignorePath)) return null;
|
|
1989
|
-
const raw = await
|
|
2114
|
+
const raw = await import_node_fs8.promises.readFile(gitignorePath, "utf8");
|
|
1990
2115
|
return (0, import_ignore.default)().add(raw);
|
|
1991
2116
|
}
|
|
1992
2117
|
async function walkDirs(start, scanPath, options, visit) {
|
|
1993
2118
|
async function recurse(current, depth) {
|
|
1994
2119
|
if (depth > options.maxDepth) return;
|
|
1995
|
-
const entries = await
|
|
2120
|
+
const entries = await import_node_fs8.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1996
2121
|
for (const entry2 of entries) {
|
|
1997
2122
|
if (!entry2.isDirectory()) continue;
|
|
1998
2123
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
1999
|
-
const child =
|
|
2124
|
+
const child = import_node_path8.default.join(current, entry2.name);
|
|
2000
2125
|
if (options.ig) {
|
|
2001
|
-
const rel =
|
|
2126
|
+
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2002
2127
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2003
2128
|
}
|
|
2004
2129
|
await visit(child);
|
|
@@ -2013,8 +2138,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2013
2138
|
for (const raw of globs) {
|
|
2014
2139
|
const pattern = raw.replace(/^\.\//, "");
|
|
2015
2140
|
if (!pattern.includes("*")) {
|
|
2016
|
-
const candidate =
|
|
2017
|
-
if (await exists(
|
|
2141
|
+
const candidate = import_node_path8.default.join(scanPath, pattern);
|
|
2142
|
+
if (await exists(import_node_path8.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2018
2143
|
continue;
|
|
2019
2144
|
}
|
|
2020
2145
|
const segments = pattern.split("/");
|
|
@@ -2023,13 +2148,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2023
2148
|
if (seg.includes("*")) break;
|
|
2024
2149
|
staticSegments.push(seg);
|
|
2025
2150
|
}
|
|
2026
|
-
const start =
|
|
2151
|
+
const start = import_node_path8.default.join(scanPath, ...staticSegments);
|
|
2027
2152
|
if (!await exists(start)) continue;
|
|
2028
2153
|
const hasDoubleStar = pattern.includes("**");
|
|
2029
2154
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2030
2155
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2031
|
-
const rel =
|
|
2032
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
2156
|
+
const rel = import_node_path8.default.relative(scanPath, dir).split(import_node_path8.default.sep).join("/");
|
|
2157
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2033
2158
|
found.add(dir);
|
|
2034
2159
|
}
|
|
2035
2160
|
});
|
|
@@ -2037,15 +2162,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2037
2162
|
return [...found];
|
|
2038
2163
|
}
|
|
2039
2164
|
async function discoverNodeService(scanPath, dir) {
|
|
2040
|
-
const pkgPath =
|
|
2165
|
+
const pkgPath = import_node_path8.default.join(dir, "package.json");
|
|
2041
2166
|
if (!await exists(pkgPath)) return null;
|
|
2042
2167
|
let pkg;
|
|
2043
2168
|
try {
|
|
2044
2169
|
pkg = await readJson(pkgPath);
|
|
2045
2170
|
} catch (err) {
|
|
2046
|
-
|
|
2047
|
-
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2048
|
-
);
|
|
2171
|
+
recordExtractionError("services", import_node_path8.default.relative(scanPath, pkgPath), err);
|
|
2049
2172
|
return null;
|
|
2050
2173
|
}
|
|
2051
2174
|
if (!pkg.name) return null;
|
|
@@ -2056,7 +2179,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2056
2179
|
language: "javascript",
|
|
2057
2180
|
version: pkg.version,
|
|
2058
2181
|
dependencies: pkg.dependencies ?? {},
|
|
2059
|
-
repoPath:
|
|
2182
|
+
repoPath: import_node_path8.default.relative(scanPath, dir),
|
|
2060
2183
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2061
2184
|
};
|
|
2062
2185
|
return { pkg, dir, node };
|
|
@@ -2072,19 +2195,21 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2072
2195
|
language: "python",
|
|
2073
2196
|
version: py.version,
|
|
2074
2197
|
dependencies: py.dependencies,
|
|
2075
|
-
repoPath:
|
|
2198
|
+
repoPath: import_node_path8.default.relative(scanPath, dir)
|
|
2076
2199
|
};
|
|
2077
2200
|
return { pkg, dir, node };
|
|
2078
2201
|
}
|
|
2079
2202
|
async function discoverServices(scanPath) {
|
|
2080
|
-
const rootPkgPath =
|
|
2203
|
+
const rootPkgPath = import_node_path8.default.join(scanPath, "package.json");
|
|
2081
2204
|
let rootPkg = null;
|
|
2082
2205
|
if (await exists(rootPkgPath)) {
|
|
2083
2206
|
try {
|
|
2084
2207
|
rootPkg = await readJson(rootPkgPath);
|
|
2085
2208
|
} catch (err) {
|
|
2086
|
-
|
|
2087
|
-
|
|
2209
|
+
recordExtractionError(
|
|
2210
|
+
"services workspaces",
|
|
2211
|
+
import_node_path8.default.relative(scanPath, rootPkgPath),
|
|
2212
|
+
err
|
|
2088
2213
|
);
|
|
2089
2214
|
}
|
|
2090
2215
|
}
|
|
@@ -2100,9 +2225,9 @@ async function discoverServices(scanPath) {
|
|
|
2100
2225
|
scanPath,
|
|
2101
2226
|
{ maxDepth: parseScanDepth(), ig },
|
|
2102
2227
|
async (dir) => {
|
|
2103
|
-
if (await exists(
|
|
2228
|
+
if (await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2104
2229
|
candidateDirs.push(dir);
|
|
2105
|
-
} else if (await exists(
|
|
2230
|
+
} else if (await exists(import_node_path8.default.join(dir, "pyproject.toml")) || await exists(import_node_path8.default.join(dir, "requirements.txt")) || await exists(import_node_path8.default.join(dir, "setup.py"))) {
|
|
2106
2231
|
candidateDirs.push(dir);
|
|
2107
2232
|
}
|
|
2108
2233
|
}
|
|
@@ -2116,8 +2241,8 @@ async function discoverServices(scanPath) {
|
|
|
2116
2241
|
if (!service) continue;
|
|
2117
2242
|
const existingDir = seen.get(service.node.name);
|
|
2118
2243
|
if (existingDir !== void 0) {
|
|
2119
|
-
const a =
|
|
2120
|
-
const b =
|
|
2244
|
+
const a = import_node_path8.default.relative(scanPath, existingDir) || ".";
|
|
2245
|
+
const b = import_node_path8.default.relative(scanPath, dir) || ".";
|
|
2121
2246
|
console.warn(
|
|
2122
2247
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2123
2248
|
);
|
|
@@ -2154,8 +2279,8 @@ function addServiceNodes(graph, services) {
|
|
|
2154
2279
|
|
|
2155
2280
|
// src/extract/aliases.ts
|
|
2156
2281
|
init_cjs_shims();
|
|
2157
|
-
var
|
|
2158
|
-
var
|
|
2282
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2283
|
+
var import_node_fs9 = require("fs");
|
|
2159
2284
|
var import_yaml2 = require("yaml");
|
|
2160
2285
|
var import_types6 = require("@neat.is/types");
|
|
2161
2286
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2182,14 +2307,14 @@ function indexServicesByName(services) {
|
|
|
2182
2307
|
const map = /* @__PURE__ */ new Map();
|
|
2183
2308
|
for (const s of services) {
|
|
2184
2309
|
map.set(s.node.name, s.node.id);
|
|
2185
|
-
map.set(
|
|
2310
|
+
map.set(import_node_path9.default.basename(s.dir), s.node.id);
|
|
2186
2311
|
}
|
|
2187
2312
|
return map;
|
|
2188
2313
|
}
|
|
2189
2314
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2190
2315
|
let composePath = null;
|
|
2191
2316
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2192
|
-
const abs =
|
|
2317
|
+
const abs = import_node_path9.default.join(scanPath, name);
|
|
2193
2318
|
if (await exists(abs)) {
|
|
2194
2319
|
composePath = abs;
|
|
2195
2320
|
break;
|
|
@@ -2200,8 +2325,10 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
2200
2325
|
try {
|
|
2201
2326
|
compose = await readYaml(composePath);
|
|
2202
2327
|
} catch (err) {
|
|
2203
|
-
|
|
2204
|
-
|
|
2328
|
+
recordExtractionError(
|
|
2329
|
+
"aliases compose",
|
|
2330
|
+
import_node_path9.default.relative(scanPath, composePath),
|
|
2331
|
+
err
|
|
2205
2332
|
);
|
|
2206
2333
|
return;
|
|
2207
2334
|
}
|
|
@@ -2243,15 +2370,13 @@ function parseDockerfileLabels(content) {
|
|
|
2243
2370
|
}
|
|
2244
2371
|
async function collectDockerfileAliases(graph, services) {
|
|
2245
2372
|
for (const service of services) {
|
|
2246
|
-
const dockerfilePath =
|
|
2373
|
+
const dockerfilePath = import_node_path9.default.join(service.dir, "Dockerfile");
|
|
2247
2374
|
if (!await exists(dockerfilePath)) continue;
|
|
2248
2375
|
let content;
|
|
2249
2376
|
try {
|
|
2250
|
-
content = await
|
|
2377
|
+
content = await import_node_fs9.promises.readFile(dockerfilePath, "utf8");
|
|
2251
2378
|
} catch (err) {
|
|
2252
|
-
|
|
2253
|
-
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2254
|
-
);
|
|
2379
|
+
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
2255
2380
|
continue;
|
|
2256
2381
|
}
|
|
2257
2382
|
const aliases = parseDockerfileLabels(content);
|
|
@@ -2261,13 +2386,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2261
2386
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2262
2387
|
if (depth > max) return [];
|
|
2263
2388
|
const out = [];
|
|
2264
|
-
const entries = await
|
|
2389
|
+
const entries = await import_node_fs9.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2265
2390
|
for (const entry2 of entries) {
|
|
2266
2391
|
if (entry2.isDirectory()) {
|
|
2267
2392
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2268
|
-
out.push(...await walkYamlFiles(
|
|
2269
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2270
|
-
out.push(
|
|
2393
|
+
out.push(...await walkYamlFiles(import_node_path9.default.join(start, entry2.name), depth + 1, max));
|
|
2394
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry2.name))) {
|
|
2395
|
+
out.push(import_node_path9.default.join(start, entry2.name));
|
|
2271
2396
|
}
|
|
2272
2397
|
}
|
|
2273
2398
|
return out;
|
|
@@ -2294,7 +2419,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2294
2419
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2295
2420
|
const files = await walkYamlFiles(scanPath);
|
|
2296
2421
|
for (const file of files) {
|
|
2297
|
-
const content = await
|
|
2422
|
+
const content = await import_node_fs9.promises.readFile(file, "utf8");
|
|
2298
2423
|
let docs;
|
|
2299
2424
|
try {
|
|
2300
2425
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2319,14 +2444,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2319
2444
|
|
|
2320
2445
|
// src/extract/databases/index.ts
|
|
2321
2446
|
init_cjs_shims();
|
|
2322
|
-
var
|
|
2447
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2323
2448
|
var import_types7 = require("@neat.is/types");
|
|
2324
2449
|
|
|
2325
2450
|
// src/extract/databases/db-config-yaml.ts
|
|
2326
2451
|
init_cjs_shims();
|
|
2327
|
-
var
|
|
2452
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2328
2453
|
async function parse(serviceDir) {
|
|
2329
|
-
const yamlPath =
|
|
2454
|
+
const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
|
|
2330
2455
|
if (!await exists(yamlPath)) return [];
|
|
2331
2456
|
const raw = await readYaml(yamlPath);
|
|
2332
2457
|
return [
|
|
@@ -2344,13 +2469,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2344
2469
|
|
|
2345
2470
|
// src/extract/databases/dotenv.ts
|
|
2346
2471
|
init_cjs_shims();
|
|
2347
|
-
var
|
|
2348
|
-
var
|
|
2472
|
+
var import_node_fs11 = require("fs");
|
|
2473
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2349
2474
|
|
|
2350
2475
|
// src/extract/databases/shared.ts
|
|
2351
2476
|
init_cjs_shims();
|
|
2352
|
-
var
|
|
2353
|
-
var
|
|
2477
|
+
var import_node_fs10 = require("fs");
|
|
2478
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2354
2479
|
function schemeToEngine(scheme) {
|
|
2355
2480
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2356
2481
|
switch (s) {
|
|
@@ -2389,14 +2514,14 @@ function parseConnectionString(url) {
|
|
|
2389
2514
|
}
|
|
2390
2515
|
async function readIfExists(filePath) {
|
|
2391
2516
|
try {
|
|
2392
|
-
return await
|
|
2517
|
+
return await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2393
2518
|
} catch {
|
|
2394
2519
|
return null;
|
|
2395
2520
|
}
|
|
2396
2521
|
}
|
|
2397
2522
|
async function findFirst(serviceDir, candidates) {
|
|
2398
2523
|
for (const rel of candidates) {
|
|
2399
|
-
const abs =
|
|
2524
|
+
const abs = import_node_path11.default.join(serviceDir, rel);
|
|
2400
2525
|
const content = await readIfExists(abs);
|
|
2401
2526
|
if (content !== null) return abs;
|
|
2402
2527
|
}
|
|
@@ -2447,15 +2572,15 @@ function parseDotenvLine(line) {
|
|
|
2447
2572
|
return { key, value };
|
|
2448
2573
|
}
|
|
2449
2574
|
async function parse2(serviceDir) {
|
|
2450
|
-
const entries = await
|
|
2575
|
+
const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2451
2576
|
const configs = [];
|
|
2452
2577
|
const seen = /* @__PURE__ */ new Set();
|
|
2453
2578
|
for (const entry2 of entries) {
|
|
2454
2579
|
if (!entry2.isFile()) continue;
|
|
2455
2580
|
const match = isConfigFile(entry2.name);
|
|
2456
2581
|
if (!match.match || match.fileType !== "env") continue;
|
|
2457
|
-
const filePath =
|
|
2458
|
-
const content = await
|
|
2582
|
+
const filePath = import_node_path12.default.join(serviceDir, entry2.name);
|
|
2583
|
+
const content = await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
2459
2584
|
for (const line of content.split("\n")) {
|
|
2460
2585
|
const parsed = parseDotenvLine(line);
|
|
2461
2586
|
if (!parsed) continue;
|
|
@@ -2474,9 +2599,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2474
2599
|
|
|
2475
2600
|
// src/extract/databases/prisma.ts
|
|
2476
2601
|
init_cjs_shims();
|
|
2477
|
-
var
|
|
2602
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2478
2603
|
async function parse3(serviceDir) {
|
|
2479
|
-
const schemaPath =
|
|
2604
|
+
const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2480
2605
|
const content = await readIfExists(schemaPath);
|
|
2481
2606
|
if (!content) return [];
|
|
2482
2607
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2608,10 +2733,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2608
2733
|
|
|
2609
2734
|
// src/extract/databases/ormconfig.ts
|
|
2610
2735
|
init_cjs_shims();
|
|
2611
|
-
var
|
|
2736
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2612
2737
|
async function parse6(serviceDir) {
|
|
2613
2738
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2614
|
-
const abs =
|
|
2739
|
+
const abs = import_node_path14.default.join(serviceDir, candidate);
|
|
2615
2740
|
if (!await exists(abs)) continue;
|
|
2616
2741
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2617
2742
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2671,9 +2796,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2671
2796
|
|
|
2672
2797
|
// src/extract/databases/sequelize.ts
|
|
2673
2798
|
init_cjs_shims();
|
|
2674
|
-
var
|
|
2799
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2675
2800
|
async function parse8(serviceDir) {
|
|
2676
|
-
const configPath =
|
|
2801
|
+
const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
|
|
2677
2802
|
if (!await exists(configPath)) return [];
|
|
2678
2803
|
const raw = await readJson(configPath);
|
|
2679
2804
|
const out = [];
|
|
@@ -2700,7 +2825,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2700
2825
|
|
|
2701
2826
|
// src/extract/databases/docker-compose.ts
|
|
2702
2827
|
init_cjs_shims();
|
|
2703
|
-
var
|
|
2828
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2704
2829
|
function portFromService(svc) {
|
|
2705
2830
|
for (const raw of svc.ports ?? []) {
|
|
2706
2831
|
const str = String(raw);
|
|
@@ -2727,7 +2852,7 @@ function databaseFromEnv(svc) {
|
|
|
2727
2852
|
}
|
|
2728
2853
|
async function parse9(serviceDir) {
|
|
2729
2854
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2730
|
-
const abs =
|
|
2855
|
+
const abs = import_node_path16.default.join(serviceDir, name);
|
|
2731
2856
|
if (!await exists(abs)) continue;
|
|
2732
2857
|
const raw = await readYaml(abs);
|
|
2733
2858
|
if (!raw?.services) return [];
|
|
@@ -2905,11 +3030,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2905
3030
|
target: dbNode.id,
|
|
2906
3031
|
type: import_types7.EdgeType.CONNECTS_TO,
|
|
2907
3032
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
3033
|
+
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
3034
|
+
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3035
|
+
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3036
|
+
evidence: {
|
|
3037
|
+
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3038
|
+
}
|
|
2913
3039
|
};
|
|
2914
3040
|
if (!graph.hasEdge(edge.id)) {
|
|
2915
3041
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2935,15 +3061,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2935
3061
|
|
|
2936
3062
|
// src/extract/configs.ts
|
|
2937
3063
|
init_cjs_shims();
|
|
2938
|
-
var
|
|
2939
|
-
var
|
|
3064
|
+
var import_node_fs12 = require("fs");
|
|
3065
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2940
3066
|
var import_types8 = require("@neat.is/types");
|
|
2941
3067
|
async function walkConfigFiles(dir) {
|
|
2942
3068
|
const out = [];
|
|
2943
3069
|
async function walk(current) {
|
|
2944
|
-
const entries = await
|
|
3070
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
|
|
2945
3071
|
for (const entry2 of entries) {
|
|
2946
|
-
const full =
|
|
3072
|
+
const full = import_node_path18.default.join(current, entry2.name);
|
|
2947
3073
|
if (entry2.isDirectory()) {
|
|
2948
3074
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
2949
3075
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
@@ -2960,13 +3086,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2960
3086
|
for (const service of services) {
|
|
2961
3087
|
const configFiles = await walkConfigFiles(service.dir);
|
|
2962
3088
|
for (const file of configFiles) {
|
|
2963
|
-
const relPath =
|
|
3089
|
+
const relPath = import_node_path18.default.relative(scanPath, file);
|
|
2964
3090
|
const node = {
|
|
2965
3091
|
id: (0, import_types8.configId)(relPath),
|
|
2966
3092
|
type: import_types8.NodeType.ConfigNode,
|
|
2967
|
-
name:
|
|
3093
|
+
name: import_node_path18.default.basename(file),
|
|
2968
3094
|
path: relPath,
|
|
2969
|
-
fileType: isConfigFile(
|
|
3095
|
+
fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
|
|
2970
3096
|
};
|
|
2971
3097
|
if (!graph.hasNode(node.id)) {
|
|
2972
3098
|
graph.addNode(node.id, node);
|
|
@@ -2978,7 +3104,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2978
3104
|
target: node.id,
|
|
2979
3105
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
2980
3106
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
2981
|
-
evidence: { file: relPath.split(
|
|
3107
|
+
evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
|
|
2982
3108
|
};
|
|
2983
3109
|
if (!graph.hasEdge(edge.id)) {
|
|
2984
3110
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2995,7 +3121,7 @@ var import_types14 = require("@neat.is/types");
|
|
|
2995
3121
|
|
|
2996
3122
|
// src/extract/calls/http.ts
|
|
2997
3123
|
init_cjs_shims();
|
|
2998
|
-
var
|
|
3124
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
2999
3125
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3000
3126
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3001
3127
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -3003,17 +3129,17 @@ var import_types9 = require("@neat.is/types");
|
|
|
3003
3129
|
|
|
3004
3130
|
// src/extract/calls/shared.ts
|
|
3005
3131
|
init_cjs_shims();
|
|
3006
|
-
var
|
|
3007
|
-
var
|
|
3132
|
+
var import_node_fs13 = require("fs");
|
|
3133
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3008
3134
|
async function walkSourceFiles(dir) {
|
|
3009
3135
|
const out = [];
|
|
3010
3136
|
async function walk(current) {
|
|
3011
|
-
const entries = await
|
|
3137
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3012
3138
|
for (const entry2 of entries) {
|
|
3013
|
-
const full =
|
|
3139
|
+
const full = import_node_path19.default.join(current, entry2.name);
|
|
3014
3140
|
if (entry2.isDirectory()) {
|
|
3015
3141
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
3016
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3142
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3017
3143
|
out.push(full);
|
|
3018
3144
|
}
|
|
3019
3145
|
}
|
|
@@ -3026,7 +3152,7 @@ async function loadSourceFiles(dir) {
|
|
|
3026
3152
|
const out = [];
|
|
3027
3153
|
for (const p of paths) {
|
|
3028
3154
|
try {
|
|
3029
|
-
const content = await
|
|
3155
|
+
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3030
3156
|
out.push({ path: p, content });
|
|
3031
3157
|
} catch {
|
|
3032
3158
|
}
|
|
@@ -3045,8 +3171,27 @@ function snippet(text, line) {
|
|
|
3045
3171
|
|
|
3046
3172
|
// src/extract/calls/http.ts
|
|
3047
3173
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3174
|
+
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3175
|
+
function isInsideJsxExternalLink(node) {
|
|
3176
|
+
let cursor = node.parent;
|
|
3177
|
+
while (cursor) {
|
|
3178
|
+
if (cursor.type === "jsx_attribute") {
|
|
3179
|
+
let owner = cursor.parent;
|
|
3180
|
+
while (owner && owner.type !== "jsx_opening_element" && owner.type !== "jsx_self_closing_element") {
|
|
3181
|
+
owner = owner.parent;
|
|
3182
|
+
}
|
|
3183
|
+
if (!owner) return false;
|
|
3184
|
+
const tagNode = owner.namedChild(0);
|
|
3185
|
+
const tagName = tagNode?.text ?? "";
|
|
3186
|
+
const right = tagName.includes(".") ? tagName.split(".").pop() : tagName;
|
|
3187
|
+
return JSX_EXTERNAL_LINK_TAGS.has(right);
|
|
3188
|
+
}
|
|
3189
|
+
cursor = cursor.parent;
|
|
3190
|
+
}
|
|
3191
|
+
return false;
|
|
3192
|
+
}
|
|
3048
3193
|
function collectStringLiterals(node, out) {
|
|
3049
|
-
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push(node.text);
|
|
3194
|
+
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
|
|
3050
3195
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3051
3196
|
const child = node.namedChild(i);
|
|
3052
3197
|
if (child) collectStringLiterals(child, out);
|
|
@@ -3058,8 +3203,9 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3058
3203
|
collectStringLiterals(tree.rootNode, literals);
|
|
3059
3204
|
const targets = /* @__PURE__ */ new Set();
|
|
3060
3205
|
for (const lit of literals) {
|
|
3206
|
+
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3061
3207
|
for (const host of knownHosts) {
|
|
3062
|
-
if (lit.
|
|
3208
|
+
if (urlMatchesHost(lit.text, host)) {
|
|
3063
3209
|
targets.add(host);
|
|
3064
3210
|
}
|
|
3065
3211
|
}
|
|
@@ -3082,9 +3228,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3082
3228
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3083
3229
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3084
3230
|
for (const service of services) {
|
|
3085
|
-
knownHosts.add(
|
|
3231
|
+
knownHosts.add(import_node_path20.default.basename(service.dir));
|
|
3086
3232
|
knownHosts.add(service.pkg.name);
|
|
3087
|
-
hostToNodeId.set(
|
|
3233
|
+
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3088
3234
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3089
3235
|
}
|
|
3090
3236
|
let edgesAdded = 0;
|
|
@@ -3092,14 +3238,13 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3092
3238
|
const files = await loadSourceFiles(service.dir);
|
|
3093
3239
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3094
3240
|
for (const file of files) {
|
|
3095
|
-
|
|
3241
|
+
if (isTestPath(file.path)) continue;
|
|
3242
|
+
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3096
3243
|
let targets;
|
|
3097
3244
|
try {
|
|
3098
3245
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
3099
3246
|
} catch (err) {
|
|
3100
|
-
|
|
3101
|
-
`[neat] http call extraction skipped ${file.path}: ${err.message}`
|
|
3102
|
-
);
|
|
3247
|
+
recordExtractionError("http call extraction", file.path, err);
|
|
3103
3248
|
continue;
|
|
3104
3249
|
}
|
|
3105
3250
|
for (const t of targets) {
|
|
@@ -3120,7 +3265,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3120
3265
|
type: import_types9.EdgeType.CALLS,
|
|
3121
3266
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3122
3267
|
evidence: {
|
|
3123
|
-
file:
|
|
3268
|
+
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3124
3269
|
line,
|
|
3125
3270
|
snippet: snippet(fileContent, line)
|
|
3126
3271
|
}
|
|
@@ -3136,7 +3281,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3136
3281
|
|
|
3137
3282
|
// src/extract/calls/kafka.ts
|
|
3138
3283
|
init_cjs_shims();
|
|
3139
|
-
var
|
|
3284
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3140
3285
|
var import_types10 = require("@neat.is/types");
|
|
3141
3286
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3142
3287
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3163,7 +3308,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3163
3308
|
kind: "kafka-topic",
|
|
3164
3309
|
edgeType,
|
|
3165
3310
|
evidence: {
|
|
3166
|
-
file:
|
|
3311
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3167
3312
|
line,
|
|
3168
3313
|
snippet: snippet(file.content, line)
|
|
3169
3314
|
}
|
|
@@ -3176,7 +3321,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3176
3321
|
|
|
3177
3322
|
// src/extract/calls/redis.ts
|
|
3178
3323
|
init_cjs_shims();
|
|
3179
|
-
var
|
|
3324
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3180
3325
|
var import_types11 = require("@neat.is/types");
|
|
3181
3326
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3182
3327
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3195,7 +3340,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3195
3340
|
kind: "redis",
|
|
3196
3341
|
edgeType: "CALLS",
|
|
3197
3342
|
evidence: {
|
|
3198
|
-
file:
|
|
3343
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3199
3344
|
line,
|
|
3200
3345
|
snippet: snippet(file.content, line)
|
|
3201
3346
|
}
|
|
@@ -3206,7 +3351,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3206
3351
|
|
|
3207
3352
|
// src/extract/calls/aws.ts
|
|
3208
3353
|
init_cjs_shims();
|
|
3209
|
-
var
|
|
3354
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3210
3355
|
var import_types12 = require("@neat.is/types");
|
|
3211
3356
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3212
3357
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3236,7 +3381,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3236
3381
|
kind,
|
|
3237
3382
|
edgeType: "CALLS",
|
|
3238
3383
|
evidence: {
|
|
3239
|
-
file:
|
|
3384
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3240
3385
|
line,
|
|
3241
3386
|
snippet: snippet(file.content, line)
|
|
3242
3387
|
}
|
|
@@ -3261,16 +3406,42 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3261
3406
|
|
|
3262
3407
|
// src/extract/calls/grpc.ts
|
|
3263
3408
|
init_cjs_shims();
|
|
3264
|
-
var
|
|
3409
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3265
3410
|
var import_types13 = require("@neat.is/types");
|
|
3266
3411
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3412
|
+
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3413
|
+
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
3267
3414
|
function isLikelyAddress(value) {
|
|
3268
3415
|
if (!value) return false;
|
|
3269
3416
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
3270
3417
|
}
|
|
3418
|
+
function normaliseForMatch(s) {
|
|
3419
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3420
|
+
}
|
|
3421
|
+
function readImports(content) {
|
|
3422
|
+
const awsSdkSuffixes = /* @__PURE__ */ new Map();
|
|
3423
|
+
AWS_SDK_IMPORT_RE.lastIndex = 0;
|
|
3424
|
+
let m;
|
|
3425
|
+
while ((m = AWS_SDK_IMPORT_RE.exec(content)) !== null) {
|
|
3426
|
+
const raw = m[1];
|
|
3427
|
+
awsSdkSuffixes.set(normaliseForMatch(raw), raw);
|
|
3428
|
+
}
|
|
3429
|
+
return {
|
|
3430
|
+
awsSdkSuffixes,
|
|
3431
|
+
hasGrpcImport: GRPC_IMPORT_RE.test(content)
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
function classifyClient(symbol, ctx) {
|
|
3435
|
+
const key = normaliseForMatch(symbol);
|
|
3436
|
+
const awsRaw = ctx.awsSdkSuffixes.get(key);
|
|
3437
|
+
if (awsRaw) return { kind: `aws-${awsRaw}` };
|
|
3438
|
+
if (ctx.hasGrpcImport) return { kind: "grpc-service" };
|
|
3439
|
+
return null;
|
|
3440
|
+
}
|
|
3271
3441
|
function grpcEndpointsFromFile(file, serviceDir) {
|
|
3272
3442
|
const out = [];
|
|
3273
3443
|
const seen = /* @__PURE__ */ new Set();
|
|
3444
|
+
const ctx = readImports(file.content);
|
|
3274
3445
|
GRPC_CLIENT_RE.lastIndex = 0;
|
|
3275
3446
|
let m;
|
|
3276
3447
|
while ((m = GRPC_CLIENT_RE.exec(file.content)) !== null) {
|
|
@@ -3278,15 +3449,18 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3278
3449
|
const addr = m[2]?.trim();
|
|
3279
3450
|
const name = isLikelyAddress(addr) ? addr : symbol;
|
|
3280
3451
|
if (seen.has(name)) continue;
|
|
3452
|
+
const classified = classifyClient(symbol, ctx);
|
|
3453
|
+
if (!classified) continue;
|
|
3281
3454
|
seen.add(name);
|
|
3455
|
+
const { kind } = classified;
|
|
3282
3456
|
const line = lineOf(file.content, m[0]);
|
|
3283
3457
|
out.push({
|
|
3284
|
-
infraId: (0, import_types13.infraId)(
|
|
3458
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3285
3459
|
name,
|
|
3286
|
-
kind
|
|
3460
|
+
kind,
|
|
3287
3461
|
edgeType: "CALLS",
|
|
3288
3462
|
evidence: {
|
|
3289
|
-
file:
|
|
3463
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3290
3464
|
line,
|
|
3291
3465
|
snippet: snippet(file.content, line)
|
|
3292
3466
|
}
|
|
@@ -3306,6 +3480,9 @@ function edgeTypeFromEndpoint(ep) {
|
|
|
3306
3480
|
return import_types14.EdgeType.CALLS;
|
|
3307
3481
|
}
|
|
3308
3482
|
}
|
|
3483
|
+
function isAwsKind(kind) {
|
|
3484
|
+
return kind.startsWith("aws-") || kind.startsWith("s3") || kind.startsWith("dynamodb");
|
|
3485
|
+
}
|
|
3309
3486
|
async function addExternalEndpointEdges(graph, services) {
|
|
3310
3487
|
let nodesAdded = 0;
|
|
3311
3488
|
let edgesAdded = 0;
|
|
@@ -3313,10 +3490,13 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3313
3490
|
const files = await loadSourceFiles(service.dir);
|
|
3314
3491
|
const endpoints = [];
|
|
3315
3492
|
for (const file of files) {
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
endpoints.push(...
|
|
3493
|
+
if (isTestPath(file.path)) continue;
|
|
3494
|
+
const masked = maskCommentsInSource(file.content);
|
|
3495
|
+
const maskedFile = { path: file.path, content: masked };
|
|
3496
|
+
endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
|
|
3497
|
+
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
3498
|
+
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
3499
|
+
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
3320
3500
|
}
|
|
3321
3501
|
if (endpoints.length === 0) continue;
|
|
3322
3502
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
@@ -3326,7 +3506,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3326
3506
|
id: ep.infraId,
|
|
3327
3507
|
type: import_types14.NodeType.InfraNode,
|
|
3328
3508
|
name: ep.name,
|
|
3329
|
-
|
|
3509
|
+
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3510
|
+
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
3511
|
+
// bucket / table kinds from aws.ts.
|
|
3512
|
+
provider: isAwsKind(ep.kind) ? "aws" : "self",
|
|
3330
3513
|
kind: ep.kind
|
|
3331
3514
|
};
|
|
3332
3515
|
graph.addNode(node.id, node);
|
|
@@ -3366,7 +3549,7 @@ init_cjs_shims();
|
|
|
3366
3549
|
|
|
3367
3550
|
// src/extract/infra/docker-compose.ts
|
|
3368
3551
|
init_cjs_shims();
|
|
3369
|
-
var
|
|
3552
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3370
3553
|
var import_types16 = require("@neat.is/types");
|
|
3371
3554
|
|
|
3372
3555
|
// src/extract/infra/shared.ts
|
|
@@ -3404,7 +3587,7 @@ function dependsOnList(value) {
|
|
|
3404
3587
|
}
|
|
3405
3588
|
function serviceNameToServiceNode(name, services) {
|
|
3406
3589
|
for (const s of services) {
|
|
3407
|
-
if (s.node.name === name ||
|
|
3590
|
+
if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
|
|
3408
3591
|
}
|
|
3409
3592
|
return null;
|
|
3410
3593
|
}
|
|
@@ -3413,7 +3596,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3413
3596
|
let edgesAdded = 0;
|
|
3414
3597
|
let composePath = null;
|
|
3415
3598
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3416
|
-
const abs =
|
|
3599
|
+
const abs = import_node_path25.default.join(scanPath, name);
|
|
3417
3600
|
if (await exists(abs)) {
|
|
3418
3601
|
composePath = abs;
|
|
3419
3602
|
break;
|
|
@@ -3424,13 +3607,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3424
3607
|
try {
|
|
3425
3608
|
compose = await readYaml(composePath);
|
|
3426
3609
|
} catch (err) {
|
|
3427
|
-
|
|
3428
|
-
|
|
3610
|
+
recordExtractionError(
|
|
3611
|
+
"infra docker-compose",
|
|
3612
|
+
import_node_path25.default.relative(scanPath, composePath),
|
|
3613
|
+
err
|
|
3429
3614
|
);
|
|
3430
3615
|
return { nodesAdded, edgesAdded };
|
|
3431
3616
|
}
|
|
3432
3617
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3433
|
-
const evidenceFile =
|
|
3618
|
+
const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
|
|
3434
3619
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3435
3620
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3436
3621
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3471,8 +3656,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3471
3656
|
|
|
3472
3657
|
// src/extract/infra/dockerfile.ts
|
|
3473
3658
|
init_cjs_shims();
|
|
3474
|
-
var
|
|
3475
|
-
var
|
|
3659
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3660
|
+
var import_node_fs14 = require("fs");
|
|
3476
3661
|
var import_types17 = require("@neat.is/types");
|
|
3477
3662
|
function runtimeImage(content) {
|
|
3478
3663
|
const lines = content.split("\n");
|
|
@@ -3492,14 +3677,16 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3492
3677
|
let nodesAdded = 0;
|
|
3493
3678
|
let edgesAdded = 0;
|
|
3494
3679
|
for (const service of services) {
|
|
3495
|
-
const dockerfilePath =
|
|
3680
|
+
const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
|
|
3496
3681
|
if (!await exists(dockerfilePath)) continue;
|
|
3497
3682
|
let content;
|
|
3498
3683
|
try {
|
|
3499
|
-
content = await
|
|
3684
|
+
content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
|
|
3500
3685
|
} catch (err) {
|
|
3501
|
-
|
|
3502
|
-
|
|
3686
|
+
recordExtractionError(
|
|
3687
|
+
"infra dockerfile",
|
|
3688
|
+
import_node_path26.default.relative(scanPath, dockerfilePath),
|
|
3689
|
+
err
|
|
3503
3690
|
);
|
|
3504
3691
|
continue;
|
|
3505
3692
|
}
|
|
@@ -3519,7 +3706,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3519
3706
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3520
3707
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3521
3708
|
evidence: {
|
|
3522
|
-
file:
|
|
3709
|
+
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
3523
3710
|
}
|
|
3524
3711
|
};
|
|
3525
3712
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3531,19 +3718,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3531
3718
|
|
|
3532
3719
|
// src/extract/infra/terraform.ts
|
|
3533
3720
|
init_cjs_shims();
|
|
3534
|
-
var
|
|
3535
|
-
var
|
|
3721
|
+
var import_node_fs15 = require("fs");
|
|
3722
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3536
3723
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3537
3724
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3538
3725
|
if (depth > max) return [];
|
|
3539
3726
|
const out = [];
|
|
3540
|
-
const entries = await
|
|
3727
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3541
3728
|
for (const entry2 of entries) {
|
|
3542
3729
|
if (entry2.isDirectory()) {
|
|
3543
3730
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
3544
|
-
out.push(...await walkTfFiles(
|
|
3731
|
+
out.push(...await walkTfFiles(import_node_path27.default.join(start, entry2.name), depth + 1, max));
|
|
3545
3732
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
3546
|
-
out.push(
|
|
3733
|
+
out.push(import_node_path27.default.join(start, entry2.name));
|
|
3547
3734
|
}
|
|
3548
3735
|
}
|
|
3549
3736
|
return out;
|
|
@@ -3552,7 +3739,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3552
3739
|
let nodesAdded = 0;
|
|
3553
3740
|
const files = await walkTfFiles(scanPath);
|
|
3554
3741
|
for (const file of files) {
|
|
3555
|
-
const content = await
|
|
3742
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3556
3743
|
RESOURCE_RE.lastIndex = 0;
|
|
3557
3744
|
let m;
|
|
3558
3745
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3570,8 +3757,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3570
3757
|
|
|
3571
3758
|
// src/extract/infra/k8s.ts
|
|
3572
3759
|
init_cjs_shims();
|
|
3573
|
-
var
|
|
3574
|
-
var
|
|
3760
|
+
var import_node_fs16 = require("fs");
|
|
3761
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3575
3762
|
var import_yaml3 = require("yaml");
|
|
3576
3763
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3577
3764
|
Service: "k8s-service",
|
|
@@ -3585,13 +3772,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3585
3772
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3586
3773
|
if (depth > max) return [];
|
|
3587
3774
|
const out = [];
|
|
3588
|
-
const entries = await
|
|
3775
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3589
3776
|
for (const entry2 of entries) {
|
|
3590
3777
|
if (entry2.isDirectory()) {
|
|
3591
3778
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3592
|
-
out.push(...await walkYamlFiles2(
|
|
3593
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3594
|
-
out.push(
|
|
3779
|
+
out.push(...await walkYamlFiles2(import_node_path28.default.join(start, entry2.name), depth + 1, max));
|
|
3780
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
|
|
3781
|
+
out.push(import_node_path28.default.join(start, entry2.name));
|
|
3595
3782
|
}
|
|
3596
3783
|
}
|
|
3597
3784
|
return out;
|
|
@@ -3600,7 +3787,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3600
3787
|
let nodesAdded = 0;
|
|
3601
3788
|
const files = await walkYamlFiles2(scanPath);
|
|
3602
3789
|
for (const file of files) {
|
|
3603
|
-
const content = await
|
|
3790
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
3604
3791
|
let docs;
|
|
3605
3792
|
try {
|
|
3606
3793
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3634,9 +3821,34 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3634
3821
|
};
|
|
3635
3822
|
}
|
|
3636
3823
|
|
|
3824
|
+
// src/extract/retire.ts
|
|
3825
|
+
init_cjs_shims();
|
|
3826
|
+
var import_node_fs17 = require("fs");
|
|
3827
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
3828
|
+
var import_types18 = require("@neat.is/types");
|
|
3829
|
+
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
3830
|
+
const toDrop = [];
|
|
3831
|
+
const bases = [scanPath, ...serviceDirs];
|
|
3832
|
+
graph.forEachEdge((id, attrs) => {
|
|
3833
|
+
const edge = attrs;
|
|
3834
|
+
if (edge.provenance !== import_types18.Provenance.EXTRACTED) return;
|
|
3835
|
+
const evidenceFile = edge.evidence?.file;
|
|
3836
|
+
if (!evidenceFile) return;
|
|
3837
|
+
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
3838
|
+
if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
|
|
3842
|
+
if (!found) toDrop.push(id);
|
|
3843
|
+
});
|
|
3844
|
+
for (const id of toDrop) graph.dropEdge(id);
|
|
3845
|
+
return toDrop.length;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3637
3848
|
// src/extract/index.ts
|
|
3638
3849
|
async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
3639
3850
|
await ensureCompatLoaded();
|
|
3851
|
+
drainExtractionErrors();
|
|
3640
3852
|
const services = await discoverServices(scanPath);
|
|
3641
3853
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
3642
3854
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -3644,12 +3856,30 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3644
3856
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
3645
3857
|
const phase4 = await addCallEdges(graph, services);
|
|
3646
3858
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
3859
|
+
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
3860
|
+
graph,
|
|
3861
|
+
scanPath,
|
|
3862
|
+
services.map((s) => s.dir)
|
|
3863
|
+
);
|
|
3647
3864
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
3648
3865
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
3866
|
+
const errorEntries = drainExtractionErrors();
|
|
3867
|
+
if (opts.errorsPath && errorEntries.length > 0) {
|
|
3868
|
+
try {
|
|
3869
|
+
await writeExtractionErrors(errorEntries, opts.errorsPath);
|
|
3870
|
+
} catch (err) {
|
|
3871
|
+
console.warn(
|
|
3872
|
+
`[neat] failed to write extraction errors to ${opts.errorsPath}: ${err.message}`
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3649
3876
|
const result = {
|
|
3650
3877
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3651
3878
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3652
|
-
frontiersPromoted
|
|
3879
|
+
frontiersPromoted,
|
|
3880
|
+
extractionErrors: errorEntries.length,
|
|
3881
|
+
errorEntries,
|
|
3882
|
+
ghostsRetired
|
|
3653
3883
|
};
|
|
3654
3884
|
emitNeatEvent({
|
|
3655
3885
|
type: "extraction-complete",
|
|
@@ -3666,8 +3896,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3666
3896
|
|
|
3667
3897
|
// src/persist.ts
|
|
3668
3898
|
init_cjs_shims();
|
|
3669
|
-
var
|
|
3670
|
-
var
|
|
3899
|
+
var import_node_fs18 = require("fs");
|
|
3900
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
3671
3901
|
var SCHEMA_VERSION = 2;
|
|
3672
3902
|
function migrateV1ToV2(payload) {
|
|
3673
3903
|
const nodes = payload.graph.nodes;
|
|
@@ -3681,7 +3911,7 @@ function migrateV1ToV2(payload) {
|
|
|
3681
3911
|
return { ...payload, schemaVersion: 2 };
|
|
3682
3912
|
}
|
|
3683
3913
|
async function ensureDir(filePath) {
|
|
3684
|
-
await
|
|
3914
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
|
|
3685
3915
|
}
|
|
3686
3916
|
async function saveGraphToDisk(graph, outPath) {
|
|
3687
3917
|
await ensureDir(outPath);
|
|
@@ -3691,13 +3921,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
3691
3921
|
graph: graph.export()
|
|
3692
3922
|
};
|
|
3693
3923
|
const tmp = `${outPath}.tmp`;
|
|
3694
|
-
await
|
|
3695
|
-
await
|
|
3924
|
+
await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
3925
|
+
await import_node_fs18.promises.rename(tmp, outPath);
|
|
3696
3926
|
}
|
|
3697
3927
|
async function loadGraphFromDisk(graph, outPath) {
|
|
3698
3928
|
let raw;
|
|
3699
3929
|
try {
|
|
3700
|
-
raw = await
|
|
3930
|
+
raw = await import_node_fs18.promises.readFile(outPath, "utf8");
|
|
3701
3931
|
} catch (err) {
|
|
3702
3932
|
if (err.code === "ENOENT") return;
|
|
3703
3933
|
throw err;
|
|
@@ -3750,23 +3980,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
3750
3980
|
|
|
3751
3981
|
// src/projects.ts
|
|
3752
3982
|
init_cjs_shims();
|
|
3753
|
-
var
|
|
3983
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
3754
3984
|
function pathsForProject(project, baseDir) {
|
|
3755
3985
|
if (project === DEFAULT_PROJECT) {
|
|
3756
3986
|
return {
|
|
3757
|
-
snapshotPath:
|
|
3758
|
-
errorsPath:
|
|
3759
|
-
staleEventsPath:
|
|
3760
|
-
embeddingsCachePath:
|
|
3761
|
-
policyViolationsPath:
|
|
3987
|
+
snapshotPath: import_node_path31.default.join(baseDir, "graph.json"),
|
|
3988
|
+
errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
|
|
3989
|
+
staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
|
|
3990
|
+
embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
|
|
3991
|
+
policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
|
|
3762
3992
|
};
|
|
3763
3993
|
}
|
|
3764
3994
|
return {
|
|
3765
|
-
snapshotPath:
|
|
3766
|
-
errorsPath:
|
|
3767
|
-
staleEventsPath:
|
|
3768
|
-
embeddingsCachePath:
|
|
3769
|
-
policyViolationsPath:
|
|
3995
|
+
snapshotPath: import_node_path31.default.join(baseDir, `${project}.json`),
|
|
3996
|
+
errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
|
|
3997
|
+
staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
3998
|
+
embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
|
|
3999
|
+
policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3770
4000
|
};
|
|
3771
4001
|
}
|
|
3772
4002
|
var Projects = class {
|
|
@@ -3804,11 +4034,11 @@ var Projects = class {
|
|
|
3804
4034
|
init_cjs_shims();
|
|
3805
4035
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
3806
4036
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
3807
|
-
var
|
|
4037
|
+
var import_types21 = require("@neat.is/types");
|
|
3808
4038
|
|
|
3809
4039
|
// src/divergences.ts
|
|
3810
4040
|
init_cjs_shims();
|
|
3811
|
-
var
|
|
4041
|
+
var import_types19 = require("@neat.is/types");
|
|
3812
4042
|
function bucketKey(source, target, type) {
|
|
3813
4043
|
return `${type}|${source}|${target}`;
|
|
3814
4044
|
}
|
|
@@ -3816,25 +4046,25 @@ function bucketEdges(graph) {
|
|
|
3816
4046
|
const buckets = /* @__PURE__ */ new Map();
|
|
3817
4047
|
graph.forEachEdge((id, attrs) => {
|
|
3818
4048
|
const e = attrs;
|
|
3819
|
-
const parsed = (0,
|
|
4049
|
+
const parsed = (0, import_types19.parseEdgeId)(id);
|
|
3820
4050
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
3821
4051
|
const key = bucketKey(e.source, e.target, e.type);
|
|
3822
4052
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3823
4053
|
switch (provenance) {
|
|
3824
|
-
case
|
|
4054
|
+
case import_types19.Provenance.EXTRACTED:
|
|
3825
4055
|
cur.extracted = e;
|
|
3826
4056
|
break;
|
|
3827
|
-
case
|
|
4057
|
+
case import_types19.Provenance.OBSERVED:
|
|
3828
4058
|
cur.observed = e;
|
|
3829
4059
|
break;
|
|
3830
|
-
case
|
|
4060
|
+
case import_types19.Provenance.INFERRED:
|
|
3831
4061
|
cur.inferred = e;
|
|
3832
4062
|
break;
|
|
3833
|
-
case
|
|
4063
|
+
case import_types19.Provenance.FRONTIER:
|
|
3834
4064
|
cur.frontier = e;
|
|
3835
4065
|
break;
|
|
3836
4066
|
default:
|
|
3837
|
-
if (e.provenance ===
|
|
4067
|
+
if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
|
|
3838
4068
|
}
|
|
3839
4069
|
buckets.set(key, cur);
|
|
3840
4070
|
});
|
|
@@ -3843,13 +4073,13 @@ function bucketEdges(graph) {
|
|
|
3843
4073
|
function nodeIsFrontier(graph, nodeId) {
|
|
3844
4074
|
if (!graph.hasNode(nodeId)) return false;
|
|
3845
4075
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
3846
|
-
return attrs.type ===
|
|
4076
|
+
return attrs.type === import_types19.NodeType.FrontierNode;
|
|
3847
4077
|
}
|
|
3848
4078
|
function hasAnyObservedFromSource(graph, sourceId) {
|
|
3849
4079
|
if (!graph.hasNode(sourceId)) return false;
|
|
3850
4080
|
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
3851
4081
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3852
|
-
if (e.provenance ===
|
|
4082
|
+
if (e.provenance === import_types19.Provenance.OBSERVED) return true;
|
|
3853
4083
|
}
|
|
3854
4084
|
return false;
|
|
3855
4085
|
}
|
|
@@ -3910,7 +4140,7 @@ function declaredHostFor(svc) {
|
|
|
3910
4140
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
3911
4141
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3912
4142
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3913
|
-
if (e.type ===
|
|
4143
|
+
if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
|
|
3914
4144
|
return true;
|
|
3915
4145
|
}
|
|
3916
4146
|
}
|
|
@@ -3923,10 +4153,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
3923
4153
|
const out = [];
|
|
3924
4154
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3925
4155
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3926
|
-
if (edge.type !==
|
|
3927
|
-
if (edge.provenance !==
|
|
4156
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4157
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
3928
4158
|
const target = graph.getNodeAttributes(edge.target);
|
|
3929
|
-
if (target.type !==
|
|
4159
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
3930
4160
|
const observedHost = target.host?.trim();
|
|
3931
4161
|
if (!observedHost) continue;
|
|
3932
4162
|
if (observedHost === declaredHost) continue;
|
|
@@ -3948,10 +4178,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
3948
4178
|
const deps = svc.dependencies ?? {};
|
|
3949
4179
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
3950
4180
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3951
|
-
if (edge.type !==
|
|
3952
|
-
if (edge.provenance !==
|
|
4181
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4182
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
3953
4183
|
const target = graph.getNodeAttributes(edge.target);
|
|
3954
|
-
if (target.type !==
|
|
4184
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
3955
4185
|
for (const pair of compatPairs()) {
|
|
3956
4186
|
if (pair.engine !== target.engine) continue;
|
|
3957
4187
|
const declared = deps[pair.driver];
|
|
@@ -4012,7 +4242,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4012
4242
|
}
|
|
4013
4243
|
graph.forEachNode((nodeId, attrs) => {
|
|
4014
4244
|
const n = attrs;
|
|
4015
|
-
if (n.type !==
|
|
4245
|
+
if (n.type !== import_types19.NodeType.ServiceNode) return;
|
|
4016
4246
|
const svc = n;
|
|
4017
4247
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4018
4248
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4036,7 +4266,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4036
4266
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4037
4267
|
return a.target.localeCompare(b.target);
|
|
4038
4268
|
});
|
|
4039
|
-
return
|
|
4269
|
+
return import_types19.DivergenceResultSchema.parse({
|
|
4040
4270
|
divergences: filtered,
|
|
4041
4271
|
totalAffected: filtered.length,
|
|
4042
4272
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4045,7 +4275,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4045
4275
|
|
|
4046
4276
|
// src/diff.ts
|
|
4047
4277
|
init_cjs_shims();
|
|
4048
|
-
var
|
|
4278
|
+
var import_node_fs19 = require("fs");
|
|
4049
4279
|
async function loadSnapshotForDiff(target) {
|
|
4050
4280
|
if (/^https?:\/\//i.test(target)) {
|
|
4051
4281
|
const res = await fetch(target);
|
|
@@ -4054,7 +4284,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4054
4284
|
}
|
|
4055
4285
|
return await res.json();
|
|
4056
4286
|
}
|
|
4057
|
-
const raw = await
|
|
4287
|
+
const raw = await import_node_fs19.promises.readFile(target, "utf8");
|
|
4058
4288
|
return JSON.parse(raw);
|
|
4059
4289
|
}
|
|
4060
4290
|
function indexEntries(entries) {
|
|
@@ -4122,41 +4352,41 @@ function canonicalJson(value) {
|
|
|
4122
4352
|
|
|
4123
4353
|
// src/registry.ts
|
|
4124
4354
|
init_cjs_shims();
|
|
4125
|
-
var
|
|
4355
|
+
var import_node_fs20 = require("fs");
|
|
4126
4356
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4127
|
-
var
|
|
4128
|
-
var
|
|
4357
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4358
|
+
var import_types20 = require("@neat.is/types");
|
|
4129
4359
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4130
4360
|
var LOCK_RETRY_MS = 50;
|
|
4131
4361
|
function neatHome() {
|
|
4132
4362
|
const override = process.env.NEAT_HOME;
|
|
4133
|
-
if (override && override.length > 0) return
|
|
4134
|
-
return
|
|
4363
|
+
if (override && override.length > 0) return import_node_path32.default.resolve(override);
|
|
4364
|
+
return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
|
|
4135
4365
|
}
|
|
4136
4366
|
function registryPath() {
|
|
4137
|
-
return
|
|
4367
|
+
return import_node_path32.default.join(neatHome(), "projects.json");
|
|
4138
4368
|
}
|
|
4139
4369
|
function registryLockPath() {
|
|
4140
|
-
return
|
|
4370
|
+
return import_node_path32.default.join(neatHome(), "projects.json.lock");
|
|
4141
4371
|
}
|
|
4142
4372
|
async function writeAtomically(target, contents) {
|
|
4143
|
-
await
|
|
4373
|
+
await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(target), { recursive: true });
|
|
4144
4374
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4145
|
-
const fd = await
|
|
4375
|
+
const fd = await import_node_fs20.promises.open(tmp, "w");
|
|
4146
4376
|
try {
|
|
4147
4377
|
await fd.writeFile(contents, "utf8");
|
|
4148
4378
|
await fd.sync();
|
|
4149
4379
|
} finally {
|
|
4150
4380
|
await fd.close();
|
|
4151
4381
|
}
|
|
4152
|
-
await
|
|
4382
|
+
await import_node_fs20.promises.rename(tmp, target);
|
|
4153
4383
|
}
|
|
4154
4384
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
4155
4385
|
const deadline = Date.now() + timeoutMs;
|
|
4156
|
-
await
|
|
4386
|
+
await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(lockPath), { recursive: true });
|
|
4157
4387
|
while (true) {
|
|
4158
4388
|
try {
|
|
4159
|
-
const fd = await
|
|
4389
|
+
const fd = await import_node_fs20.promises.open(lockPath, "wx");
|
|
4160
4390
|
await fd.close();
|
|
4161
4391
|
return;
|
|
4162
4392
|
} catch (err) {
|
|
@@ -4172,7 +4402,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
4172
4402
|
}
|
|
4173
4403
|
}
|
|
4174
4404
|
async function releaseLock(lockPath) {
|
|
4175
|
-
await
|
|
4405
|
+
await import_node_fs20.promises.unlink(lockPath).catch(() => {
|
|
4176
4406
|
});
|
|
4177
4407
|
}
|
|
4178
4408
|
async function withLock(fn) {
|
|
@@ -4188,7 +4418,7 @@ async function readRegistry() {
|
|
|
4188
4418
|
const file = registryPath();
|
|
4189
4419
|
let raw;
|
|
4190
4420
|
try {
|
|
4191
|
-
raw = await
|
|
4421
|
+
raw = await import_node_fs20.promises.readFile(file, "utf8");
|
|
4192
4422
|
} catch (err) {
|
|
4193
4423
|
if (err.code === "ENOENT") {
|
|
4194
4424
|
return { version: 1, projects: [] };
|
|
@@ -4196,10 +4426,10 @@ async function readRegistry() {
|
|
|
4196
4426
|
throw err;
|
|
4197
4427
|
}
|
|
4198
4428
|
const parsed = JSON.parse(raw);
|
|
4199
|
-
return
|
|
4429
|
+
return import_types20.RegistryFileSchema.parse(parsed);
|
|
4200
4430
|
}
|
|
4201
4431
|
async function writeRegistry(reg) {
|
|
4202
|
-
const validated =
|
|
4432
|
+
const validated = import_types20.RegistryFileSchema.parse(reg);
|
|
4203
4433
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4204
4434
|
}
|
|
4205
4435
|
async function getProject(name) {
|
|
@@ -4409,11 +4639,11 @@ function registerRoutes(scope, ctx) {
|
|
|
4409
4639
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4410
4640
|
const parsed = [];
|
|
4411
4641
|
for (const c of candidates) {
|
|
4412
|
-
const r =
|
|
4642
|
+
const r = import_types21.DivergenceTypeSchema.safeParse(c);
|
|
4413
4643
|
if (!r.success) {
|
|
4414
4644
|
return reply.code(400).send({
|
|
4415
4645
|
error: `unknown divergence type "${c}"`,
|
|
4416
|
-
allowed:
|
|
4646
|
+
allowed: import_types21.DivergenceTypeSchema.options
|
|
4417
4647
|
});
|
|
4418
4648
|
}
|
|
4419
4649
|
parsed.push(r.data);
|
|
@@ -4597,7 +4827,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4597
4827
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4598
4828
|
let violations = await log.readAll();
|
|
4599
4829
|
if (req2.query.severity) {
|
|
4600
|
-
const sev =
|
|
4830
|
+
const sev = import_types21.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
4601
4831
|
if (!sev.success) {
|
|
4602
4832
|
return reply.code(400).send({
|
|
4603
4833
|
error: "invalid severity",
|
|
@@ -4614,7 +4844,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4614
4844
|
scope.post("/policies/check", async (req2, reply) => {
|
|
4615
4845
|
const proj = resolveProject(registry, req2, reply);
|
|
4616
4846
|
if (!proj) return;
|
|
4617
|
-
const parsed =
|
|
4847
|
+
const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
4618
4848
|
if (!parsed.success) {
|
|
4619
4849
|
return reply.code(400).send({
|
|
4620
4850
|
error: "invalid /policies/check body",
|
|
@@ -4715,11 +4945,11 @@ async function buildApi(opts) {
|
|
|
4715
4945
|
// src/daemon.ts
|
|
4716
4946
|
init_otel();
|
|
4717
4947
|
function neatHomeFor(opts) {
|
|
4718
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
4948
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path35.default.resolve(opts.neatHome);
|
|
4719
4949
|
const env = process.env.NEAT_HOME;
|
|
4720
|
-
if (env && env.length > 0) return
|
|
4950
|
+
if (env && env.length > 0) return import_node_path35.default.resolve(env);
|
|
4721
4951
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4722
|
-
return
|
|
4952
|
+
return import_node_path35.default.join(home, ".neat");
|
|
4723
4953
|
}
|
|
4724
4954
|
function routeSpanToProject(serviceName, projects) {
|
|
4725
4955
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -4732,9 +4962,9 @@ function routeSpanToProject(serviceName, projects) {
|
|
|
4732
4962
|
return DEFAULT_PROJECT;
|
|
4733
4963
|
}
|
|
4734
4964
|
async function bootstrapProject(entry2) {
|
|
4735
|
-
const paths = pathsForProject(entry2.name,
|
|
4965
|
+
const paths = pathsForProject(entry2.name, import_node_path35.default.join(entry2.path, "neat-out"));
|
|
4736
4966
|
try {
|
|
4737
|
-
const stat = await
|
|
4967
|
+
const stat = await import_node_fs21.promises.stat(entry2.path);
|
|
4738
4968
|
if (!stat.isDirectory()) {
|
|
4739
4969
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
4740
4970
|
}
|
|
@@ -4799,13 +5029,13 @@ async function startDaemon(opts = {}) {
|
|
|
4799
5029
|
const home = neatHomeFor(opts);
|
|
4800
5030
|
const regPath = registryPath();
|
|
4801
5031
|
try {
|
|
4802
|
-
await
|
|
5032
|
+
await import_node_fs21.promises.access(regPath);
|
|
4803
5033
|
} catch {
|
|
4804
5034
|
throw new Error(
|
|
4805
5035
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
4806
5036
|
);
|
|
4807
5037
|
}
|
|
4808
|
-
const pidPath =
|
|
5038
|
+
const pidPath = import_node_path35.default.join(home, "neatd.pid");
|
|
4809
5039
|
await writeAtomically(pidPath, `${process.pid}
|
|
4810
5040
|
`);
|
|
4811
5041
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -4874,7 +5104,7 @@ async function startDaemon(opts = {}) {
|
|
|
4874
5104
|
}
|
|
4875
5105
|
if (restApp) await restApp.close().catch(() => {
|
|
4876
5106
|
});
|
|
4877
|
-
await
|
|
5107
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
4878
5108
|
});
|
|
4879
5109
|
throw new Error(
|
|
4880
5110
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -4919,7 +5149,7 @@ async function startDaemon(opts = {}) {
|
|
|
4919
5149
|
});
|
|
4920
5150
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
4921
5151
|
});
|
|
4922
|
-
await
|
|
5152
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
4923
5153
|
});
|
|
4924
5154
|
throw new Error(
|
|
4925
5155
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -4959,7 +5189,7 @@ async function startDaemon(opts = {}) {
|
|
|
4959
5189
|
} catch {
|
|
4960
5190
|
}
|
|
4961
5191
|
}
|
|
4962
|
-
await
|
|
5192
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
4963
5193
|
});
|
|
4964
5194
|
};
|
|
4965
5195
|
return { slots, reload, stop, pidPath, restAddress, otlpAddress };
|
|
@@ -4969,7 +5199,7 @@ async function startDaemon(opts = {}) {
|
|
|
4969
5199
|
init_cjs_shims();
|
|
4970
5200
|
var import_node_child_process = require("child_process");
|
|
4971
5201
|
var import_node_net = __toESM(require("net"), 1);
|
|
4972
|
-
var
|
|
5202
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
4973
5203
|
var DEFAULT_WEB_PORT = 6328;
|
|
4974
5204
|
async function assertPortFree(port) {
|
|
4975
5205
|
await new Promise((resolve, reject) => {
|
|
@@ -4997,10 +5227,10 @@ function resolveWebPackageDir() {
|
|
|
4997
5227
|
eval("require")
|
|
4998
5228
|
);
|
|
4999
5229
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
5000
|
-
return
|
|
5230
|
+
return import_node_path36.default.dirname(pkgJsonPath);
|
|
5001
5231
|
}
|
|
5002
5232
|
function resolveStandaloneServerEntry(webDir) {
|
|
5003
|
-
return
|
|
5233
|
+
return import_node_path36.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
5004
5234
|
}
|
|
5005
5235
|
async function spawnWebUI(restPort) {
|
|
5006
5236
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -5025,7 +5255,7 @@ async function spawnWebUI(restPort) {
|
|
|
5025
5255
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
5026
5256
|
};
|
|
5027
5257
|
const child = (0, import_node_child_process.spawn)(process.execPath, [serverEntry], {
|
|
5028
|
-
cwd:
|
|
5258
|
+
cwd: import_node_path36.default.dirname(serverEntry),
|
|
5029
5259
|
env,
|
|
5030
5260
|
stdio: ["ignore", "inherit", "inherit"],
|
|
5031
5261
|
detached: false
|
|
@@ -5062,14 +5292,14 @@ async function spawnWebUI(restPort) {
|
|
|
5062
5292
|
// src/neatd.ts
|
|
5063
5293
|
function neatHome2() {
|
|
5064
5294
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
5065
|
-
return
|
|
5295
|
+
return import_node_path37.default.resolve(process.env.NEAT_HOME);
|
|
5066
5296
|
}
|
|
5067
5297
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5068
|
-
return
|
|
5298
|
+
return import_node_path37.default.join(home, ".neat");
|
|
5069
5299
|
}
|
|
5070
5300
|
async function readPid() {
|
|
5071
5301
|
try {
|
|
5072
|
-
const raw = await
|
|
5302
|
+
const raw = await import_node_fs22.promises.readFile(import_node_path37.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
5073
5303
|
const n = Number.parseInt(raw.trim(), 10);
|
|
5074
5304
|
return Number.isFinite(n) ? n : null;
|
|
5075
5305
|
} catch {
|