@neat.is/core 0.3.1 → 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 +601 -306
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +100 -53
- 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 +470 -228
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +16 -4
- package/dist/neatd.js.map +1 -1
- 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/index.cjs
CHANGED
|
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req) {
|
|
|
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 }
|
|
@@ -362,12 +362,12 @@ function logSpanHandler(span) {
|
|
|
362
362
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
363
363
|
);
|
|
364
364
|
}
|
|
365
|
-
var
|
|
365
|
+
var import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
366
366
|
var init_otel = __esm({
|
|
367
367
|
"src/otel.ts"() {
|
|
368
368
|
"use strict";
|
|
369
369
|
init_cjs_shims();
|
|
370
|
-
|
|
370
|
+
import_node_path34 = __toESM(require("path"), 1);
|
|
371
371
|
import_node_url2 = require("url");
|
|
372
372
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
373
373
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -876,19 +876,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
876
876
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
877
877
|
let best = { path: [start], edges: [] };
|
|
878
878
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
879
|
-
function step(node,
|
|
880
|
-
if (
|
|
881
|
-
best = { path: [...
|
|
879
|
+
function step(node, path36, edges) {
|
|
880
|
+
if (path36.length > best.path.length) {
|
|
881
|
+
best = { path: [...path36], edges: [...edges] };
|
|
882
882
|
}
|
|
883
|
-
if (
|
|
883
|
+
if (path36.length - 1 >= maxDepth) return;
|
|
884
884
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
885
885
|
for (const [srcId, edge] of incoming) {
|
|
886
886
|
if (visited.has(srcId)) continue;
|
|
887
887
|
visited.add(srcId);
|
|
888
|
-
|
|
888
|
+
path36.push(srcId);
|
|
889
889
|
edges.push(edge);
|
|
890
|
-
step(srcId,
|
|
891
|
-
|
|
890
|
+
step(srcId, path36, edges);
|
|
891
|
+
path36.pop();
|
|
892
892
|
edges.pop();
|
|
893
893
|
visited.delete(srcId);
|
|
894
894
|
}
|
|
@@ -1925,8 +1925,8 @@ async function readErrorEvents(errorsPath) {
|
|
|
1925
1925
|
|
|
1926
1926
|
// src/extract/services.ts
|
|
1927
1927
|
init_cjs_shims();
|
|
1928
|
-
var
|
|
1929
|
-
var
|
|
1928
|
+
var import_node_fs8 = require("fs");
|
|
1929
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1930
1930
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1931
1931
|
var import_minimatch2 = require("minimatch");
|
|
1932
1932
|
var import_types5 = require("@neat.is/types");
|
|
@@ -1950,9 +1950,107 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1950
1950
|
function isConfigFile(name) {
|
|
1951
1951
|
const ext = import_node_path4.default.extname(name);
|
|
1952
1952
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
1953
|
-
if (name === ".env" || name.startsWith(".env."))
|
|
1953
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
1954
|
+
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
1955
|
+
return { match: true, fileType: "env" };
|
|
1956
|
+
}
|
|
1954
1957
|
return { match: false, fileType: "" };
|
|
1955
1958
|
}
|
|
1959
|
+
function isTestPath(filePath) {
|
|
1960
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
1961
|
+
const segments = normalised.split("/");
|
|
1962
|
+
for (const seg of segments) {
|
|
1963
|
+
if (seg === "__tests__" || seg === "__fixtures__" || seg === "integration-tests") {
|
|
1964
|
+
return true;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
const base = segments[segments.length - 1] ?? "";
|
|
1968
|
+
return /\.(spec|test)\.(?:tsx?|jsx?|mjs|cjs|py)$/i.test(base);
|
|
1969
|
+
}
|
|
1970
|
+
function isEnvTemplateFile(name) {
|
|
1971
|
+
if (name === ".env.template" || name === ".env.example" || name === ".env.sample") {
|
|
1972
|
+
return true;
|
|
1973
|
+
}
|
|
1974
|
+
return /^\.env\.[^.]+\.(?:template|example|sample)$/i.test(name);
|
|
1975
|
+
}
|
|
1976
|
+
function maskCommentsInSource(src) {
|
|
1977
|
+
const len = src.length;
|
|
1978
|
+
const out = new Array(len);
|
|
1979
|
+
let i = 0;
|
|
1980
|
+
let inString = 0;
|
|
1981
|
+
let escaped = false;
|
|
1982
|
+
while (i < len) {
|
|
1983
|
+
const c = src[i];
|
|
1984
|
+
if (inString !== 0) {
|
|
1985
|
+
out[i] = c;
|
|
1986
|
+
if (escaped) {
|
|
1987
|
+
escaped = false;
|
|
1988
|
+
} else if (c === "\\") {
|
|
1989
|
+
escaped = true;
|
|
1990
|
+
} else if (c === inString) {
|
|
1991
|
+
inString = 0;
|
|
1992
|
+
}
|
|
1993
|
+
i++;
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
if (c === "/" && i + 1 < len) {
|
|
1997
|
+
const next = src[i + 1];
|
|
1998
|
+
if (next === "/") {
|
|
1999
|
+
out[i] = " ";
|
|
2000
|
+
out[i + 1] = " ";
|
|
2001
|
+
let j = i + 2;
|
|
2002
|
+
while (j < len && src[j] !== "\n") {
|
|
2003
|
+
out[j] = " ";
|
|
2004
|
+
j++;
|
|
2005
|
+
}
|
|
2006
|
+
i = j;
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
if (next === "*") {
|
|
2010
|
+
out[i] = " ";
|
|
2011
|
+
out[i + 1] = " ";
|
|
2012
|
+
let j = i + 2;
|
|
2013
|
+
while (j < len) {
|
|
2014
|
+
if (src[j] === "\n") {
|
|
2015
|
+
out[j] = "\n";
|
|
2016
|
+
j++;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (src[j] === "*" && j + 1 < len && src[j + 1] === "/") {
|
|
2020
|
+
out[j] = " ";
|
|
2021
|
+
out[j + 1] = " ";
|
|
2022
|
+
j += 2;
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
out[j] = " ";
|
|
2026
|
+
j++;
|
|
2027
|
+
}
|
|
2028
|
+
i = j;
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
out[i] = c;
|
|
2033
|
+
if (c === "'" || c === '"' || c === "`") inString = c;
|
|
2034
|
+
i++;
|
|
2035
|
+
}
|
|
2036
|
+
return out.join("");
|
|
2037
|
+
}
|
|
2038
|
+
var URL_LIKE = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
2039
|
+
function urlMatchesHost(urlString, host) {
|
|
2040
|
+
if (typeof urlString !== "string" || urlString.length === 0) return false;
|
|
2041
|
+
if (!URL_LIKE.test(urlString)) return false;
|
|
2042
|
+
const [wantedHost, wantedPort] = host.split(":");
|
|
2043
|
+
let parsed;
|
|
2044
|
+
try {
|
|
2045
|
+
const candidate = urlString.startsWith("//") ? `http:${urlString}` : urlString;
|
|
2046
|
+
parsed = new URL(candidate);
|
|
2047
|
+
} catch {
|
|
2048
|
+
return false;
|
|
2049
|
+
}
|
|
2050
|
+
if (parsed.hostname.toLowerCase() !== (wantedHost ?? "").toLowerCase()) return false;
|
|
2051
|
+
if (wantedPort && parsed.port !== wantedPort) return false;
|
|
2052
|
+
return true;
|
|
2053
|
+
}
|
|
1956
2054
|
function cleanVersion(raw) {
|
|
1957
2055
|
if (!raw) return void 0;
|
|
1958
2056
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
@@ -2108,6 +2206,33 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2108
2206
|
return author ?? void 0;
|
|
2109
2207
|
}
|
|
2110
2208
|
|
|
2209
|
+
// src/extract/errors.ts
|
|
2210
|
+
init_cjs_shims();
|
|
2211
|
+
var import_node_fs7 = require("fs");
|
|
2212
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2213
|
+
var sink = [];
|
|
2214
|
+
function recordExtractionError(producer, file, err) {
|
|
2215
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
2216
|
+
sink.push({
|
|
2217
|
+
producer,
|
|
2218
|
+
file,
|
|
2219
|
+
error: e.message,
|
|
2220
|
+
stack: e.stack,
|
|
2221
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2222
|
+
source: "extract"
|
|
2223
|
+
});
|
|
2224
|
+
console.warn(`[neat] ${producer} skipped ${file}: ${e.message}`);
|
|
2225
|
+
}
|
|
2226
|
+
function drainExtractionErrors() {
|
|
2227
|
+
return sink.splice(0, sink.length);
|
|
2228
|
+
}
|
|
2229
|
+
async function writeExtractionErrors(errors, errorsPath) {
|
|
2230
|
+
if (errors.length === 0) return;
|
|
2231
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(errorsPath), { recursive: true });
|
|
2232
|
+
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2233
|
+
await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2111
2236
|
// src/extract/services.ts
|
|
2112
2237
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
2113
2238
|
function parseScanDepth() {
|
|
@@ -2124,21 +2249,21 @@ function workspaceGlobs(pkg) {
|
|
|
2124
2249
|
return null;
|
|
2125
2250
|
}
|
|
2126
2251
|
async function loadGitignore(scanPath) {
|
|
2127
|
-
const gitignorePath =
|
|
2252
|
+
const gitignorePath = import_node_path8.default.join(scanPath, ".gitignore");
|
|
2128
2253
|
if (!await exists(gitignorePath)) return null;
|
|
2129
|
-
const raw = await
|
|
2254
|
+
const raw = await import_node_fs8.promises.readFile(gitignorePath, "utf8");
|
|
2130
2255
|
return (0, import_ignore.default)().add(raw);
|
|
2131
2256
|
}
|
|
2132
2257
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2133
2258
|
async function recurse(current, depth) {
|
|
2134
2259
|
if (depth > options.maxDepth) return;
|
|
2135
|
-
const entries = await
|
|
2260
|
+
const entries = await import_node_fs8.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2136
2261
|
for (const entry of entries) {
|
|
2137
2262
|
if (!entry.isDirectory()) continue;
|
|
2138
2263
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2139
|
-
const child =
|
|
2264
|
+
const child = import_node_path8.default.join(current, entry.name);
|
|
2140
2265
|
if (options.ig) {
|
|
2141
|
-
const rel =
|
|
2266
|
+
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2142
2267
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2143
2268
|
}
|
|
2144
2269
|
await visit(child);
|
|
@@ -2153,8 +2278,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2153
2278
|
for (const raw of globs) {
|
|
2154
2279
|
const pattern = raw.replace(/^\.\//, "");
|
|
2155
2280
|
if (!pattern.includes("*")) {
|
|
2156
|
-
const candidate =
|
|
2157
|
-
if (await exists(
|
|
2281
|
+
const candidate = import_node_path8.default.join(scanPath, pattern);
|
|
2282
|
+
if (await exists(import_node_path8.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2158
2283
|
continue;
|
|
2159
2284
|
}
|
|
2160
2285
|
const segments = pattern.split("/");
|
|
@@ -2163,13 +2288,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2163
2288
|
if (seg.includes("*")) break;
|
|
2164
2289
|
staticSegments.push(seg);
|
|
2165
2290
|
}
|
|
2166
|
-
const start =
|
|
2291
|
+
const start = import_node_path8.default.join(scanPath, ...staticSegments);
|
|
2167
2292
|
if (!await exists(start)) continue;
|
|
2168
2293
|
const hasDoubleStar = pattern.includes("**");
|
|
2169
2294
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2170
2295
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2171
|
-
const rel =
|
|
2172
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
2296
|
+
const rel = import_node_path8.default.relative(scanPath, dir).split(import_node_path8.default.sep).join("/");
|
|
2297
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2173
2298
|
found.add(dir);
|
|
2174
2299
|
}
|
|
2175
2300
|
});
|
|
@@ -2177,15 +2302,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2177
2302
|
return [...found];
|
|
2178
2303
|
}
|
|
2179
2304
|
async function discoverNodeService(scanPath, dir) {
|
|
2180
|
-
const pkgPath =
|
|
2305
|
+
const pkgPath = import_node_path8.default.join(dir, "package.json");
|
|
2181
2306
|
if (!await exists(pkgPath)) return null;
|
|
2182
2307
|
let pkg;
|
|
2183
2308
|
try {
|
|
2184
2309
|
pkg = await readJson(pkgPath);
|
|
2185
2310
|
} catch (err) {
|
|
2186
|
-
|
|
2187
|
-
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2188
|
-
);
|
|
2311
|
+
recordExtractionError("services", import_node_path8.default.relative(scanPath, pkgPath), err);
|
|
2189
2312
|
return null;
|
|
2190
2313
|
}
|
|
2191
2314
|
if (!pkg.name) return null;
|
|
@@ -2196,7 +2319,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2196
2319
|
language: "javascript",
|
|
2197
2320
|
version: pkg.version,
|
|
2198
2321
|
dependencies: pkg.dependencies ?? {},
|
|
2199
|
-
repoPath:
|
|
2322
|
+
repoPath: import_node_path8.default.relative(scanPath, dir),
|
|
2200
2323
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2201
2324
|
};
|
|
2202
2325
|
return { pkg, dir, node };
|
|
@@ -2212,19 +2335,21 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2212
2335
|
language: "python",
|
|
2213
2336
|
version: py.version,
|
|
2214
2337
|
dependencies: py.dependencies,
|
|
2215
|
-
repoPath:
|
|
2338
|
+
repoPath: import_node_path8.default.relative(scanPath, dir)
|
|
2216
2339
|
};
|
|
2217
2340
|
return { pkg, dir, node };
|
|
2218
2341
|
}
|
|
2219
2342
|
async function discoverServices(scanPath) {
|
|
2220
|
-
const rootPkgPath =
|
|
2343
|
+
const rootPkgPath = import_node_path8.default.join(scanPath, "package.json");
|
|
2221
2344
|
let rootPkg = null;
|
|
2222
2345
|
if (await exists(rootPkgPath)) {
|
|
2223
2346
|
try {
|
|
2224
2347
|
rootPkg = await readJson(rootPkgPath);
|
|
2225
2348
|
} catch (err) {
|
|
2226
|
-
|
|
2227
|
-
|
|
2349
|
+
recordExtractionError(
|
|
2350
|
+
"services workspaces",
|
|
2351
|
+
import_node_path8.default.relative(scanPath, rootPkgPath),
|
|
2352
|
+
err
|
|
2228
2353
|
);
|
|
2229
2354
|
}
|
|
2230
2355
|
}
|
|
@@ -2240,9 +2365,9 @@ async function discoverServices(scanPath) {
|
|
|
2240
2365
|
scanPath,
|
|
2241
2366
|
{ maxDepth: parseScanDepth(), ig },
|
|
2242
2367
|
async (dir) => {
|
|
2243
|
-
if (await exists(
|
|
2368
|
+
if (await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2244
2369
|
candidateDirs.push(dir);
|
|
2245
|
-
} else if (await exists(
|
|
2370
|
+
} 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"))) {
|
|
2246
2371
|
candidateDirs.push(dir);
|
|
2247
2372
|
}
|
|
2248
2373
|
}
|
|
@@ -2256,8 +2381,8 @@ async function discoverServices(scanPath) {
|
|
|
2256
2381
|
if (!service) continue;
|
|
2257
2382
|
const existingDir = seen.get(service.node.name);
|
|
2258
2383
|
if (existingDir !== void 0) {
|
|
2259
|
-
const a =
|
|
2260
|
-
const b =
|
|
2384
|
+
const a = import_node_path8.default.relative(scanPath, existingDir) || ".";
|
|
2385
|
+
const b = import_node_path8.default.relative(scanPath, dir) || ".";
|
|
2261
2386
|
console.warn(
|
|
2262
2387
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2263
2388
|
);
|
|
@@ -2294,8 +2419,8 @@ function addServiceNodes(graph, services) {
|
|
|
2294
2419
|
|
|
2295
2420
|
// src/extract/aliases.ts
|
|
2296
2421
|
init_cjs_shims();
|
|
2297
|
-
var
|
|
2298
|
-
var
|
|
2422
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2423
|
+
var import_node_fs9 = require("fs");
|
|
2299
2424
|
var import_yaml2 = require("yaml");
|
|
2300
2425
|
var import_types6 = require("@neat.is/types");
|
|
2301
2426
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2322,14 +2447,14 @@ function indexServicesByName(services) {
|
|
|
2322
2447
|
const map = /* @__PURE__ */ new Map();
|
|
2323
2448
|
for (const s of services) {
|
|
2324
2449
|
map.set(s.node.name, s.node.id);
|
|
2325
|
-
map.set(
|
|
2450
|
+
map.set(import_node_path9.default.basename(s.dir), s.node.id);
|
|
2326
2451
|
}
|
|
2327
2452
|
return map;
|
|
2328
2453
|
}
|
|
2329
2454
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2330
2455
|
let composePath = null;
|
|
2331
2456
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2332
|
-
const abs =
|
|
2457
|
+
const abs = import_node_path9.default.join(scanPath, name);
|
|
2333
2458
|
if (await exists(abs)) {
|
|
2334
2459
|
composePath = abs;
|
|
2335
2460
|
break;
|
|
@@ -2340,8 +2465,10 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
2340
2465
|
try {
|
|
2341
2466
|
compose = await readYaml(composePath);
|
|
2342
2467
|
} catch (err) {
|
|
2343
|
-
|
|
2344
|
-
|
|
2468
|
+
recordExtractionError(
|
|
2469
|
+
"aliases compose",
|
|
2470
|
+
import_node_path9.default.relative(scanPath, composePath),
|
|
2471
|
+
err
|
|
2345
2472
|
);
|
|
2346
2473
|
return;
|
|
2347
2474
|
}
|
|
@@ -2383,15 +2510,13 @@ function parseDockerfileLabels(content) {
|
|
|
2383
2510
|
}
|
|
2384
2511
|
async function collectDockerfileAliases(graph, services) {
|
|
2385
2512
|
for (const service of services) {
|
|
2386
|
-
const dockerfilePath =
|
|
2513
|
+
const dockerfilePath = import_node_path9.default.join(service.dir, "Dockerfile");
|
|
2387
2514
|
if (!await exists(dockerfilePath)) continue;
|
|
2388
2515
|
let content;
|
|
2389
2516
|
try {
|
|
2390
|
-
content = await
|
|
2517
|
+
content = await import_node_fs9.promises.readFile(dockerfilePath, "utf8");
|
|
2391
2518
|
} catch (err) {
|
|
2392
|
-
|
|
2393
|
-
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2394
|
-
);
|
|
2519
|
+
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
2395
2520
|
continue;
|
|
2396
2521
|
}
|
|
2397
2522
|
const aliases = parseDockerfileLabels(content);
|
|
@@ -2401,13 +2526,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2401
2526
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2402
2527
|
if (depth > max) return [];
|
|
2403
2528
|
const out = [];
|
|
2404
|
-
const entries = await
|
|
2529
|
+
const entries = await import_node_fs9.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2405
2530
|
for (const entry of entries) {
|
|
2406
2531
|
if (entry.isDirectory()) {
|
|
2407
2532
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2408
|
-
out.push(...await walkYamlFiles(
|
|
2409
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2410
|
-
out.push(
|
|
2533
|
+
out.push(...await walkYamlFiles(import_node_path9.default.join(start, entry.name), depth + 1, max));
|
|
2534
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry.name))) {
|
|
2535
|
+
out.push(import_node_path9.default.join(start, entry.name));
|
|
2411
2536
|
}
|
|
2412
2537
|
}
|
|
2413
2538
|
return out;
|
|
@@ -2434,7 +2559,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2434
2559
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2435
2560
|
const files = await walkYamlFiles(scanPath);
|
|
2436
2561
|
for (const file of files) {
|
|
2437
|
-
const content = await
|
|
2562
|
+
const content = await import_node_fs9.promises.readFile(file, "utf8");
|
|
2438
2563
|
let docs;
|
|
2439
2564
|
try {
|
|
2440
2565
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2459,14 +2584,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2459
2584
|
|
|
2460
2585
|
// src/extract/databases/index.ts
|
|
2461
2586
|
init_cjs_shims();
|
|
2462
|
-
var
|
|
2587
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2463
2588
|
var import_types7 = require("@neat.is/types");
|
|
2464
2589
|
|
|
2465
2590
|
// src/extract/databases/db-config-yaml.ts
|
|
2466
2591
|
init_cjs_shims();
|
|
2467
|
-
var
|
|
2592
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2468
2593
|
async function parse(serviceDir) {
|
|
2469
|
-
const yamlPath =
|
|
2594
|
+
const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
|
|
2470
2595
|
if (!await exists(yamlPath)) return [];
|
|
2471
2596
|
const raw = await readYaml(yamlPath);
|
|
2472
2597
|
return [
|
|
@@ -2484,13 +2609,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2484
2609
|
|
|
2485
2610
|
// src/extract/databases/dotenv.ts
|
|
2486
2611
|
init_cjs_shims();
|
|
2487
|
-
var
|
|
2488
|
-
var
|
|
2612
|
+
var import_node_fs11 = require("fs");
|
|
2613
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2489
2614
|
|
|
2490
2615
|
// src/extract/databases/shared.ts
|
|
2491
2616
|
init_cjs_shims();
|
|
2492
|
-
var
|
|
2493
|
-
var
|
|
2617
|
+
var import_node_fs10 = require("fs");
|
|
2618
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2494
2619
|
function schemeToEngine(scheme) {
|
|
2495
2620
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2496
2621
|
switch (s) {
|
|
@@ -2529,14 +2654,14 @@ function parseConnectionString(url) {
|
|
|
2529
2654
|
}
|
|
2530
2655
|
async function readIfExists(filePath) {
|
|
2531
2656
|
try {
|
|
2532
|
-
return await
|
|
2657
|
+
return await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2533
2658
|
} catch {
|
|
2534
2659
|
return null;
|
|
2535
2660
|
}
|
|
2536
2661
|
}
|
|
2537
2662
|
async function findFirst(serviceDir, candidates) {
|
|
2538
2663
|
for (const rel of candidates) {
|
|
2539
|
-
const abs =
|
|
2664
|
+
const abs = import_node_path11.default.join(serviceDir, rel);
|
|
2540
2665
|
const content = await readIfExists(abs);
|
|
2541
2666
|
if (content !== null) return abs;
|
|
2542
2667
|
}
|
|
@@ -2587,15 +2712,15 @@ function parseDotenvLine(line) {
|
|
|
2587
2712
|
return { key, value };
|
|
2588
2713
|
}
|
|
2589
2714
|
async function parse2(serviceDir) {
|
|
2590
|
-
const entries = await
|
|
2715
|
+
const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2591
2716
|
const configs = [];
|
|
2592
2717
|
const seen = /* @__PURE__ */ new Set();
|
|
2593
2718
|
for (const entry of entries) {
|
|
2594
2719
|
if (!entry.isFile()) continue;
|
|
2595
2720
|
const match = isConfigFile(entry.name);
|
|
2596
2721
|
if (!match.match || match.fileType !== "env") continue;
|
|
2597
|
-
const filePath =
|
|
2598
|
-
const content = await
|
|
2722
|
+
const filePath = import_node_path12.default.join(serviceDir, entry.name);
|
|
2723
|
+
const content = await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
2599
2724
|
for (const line of content.split("\n")) {
|
|
2600
2725
|
const parsed = parseDotenvLine(line);
|
|
2601
2726
|
if (!parsed) continue;
|
|
@@ -2614,9 +2739,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2614
2739
|
|
|
2615
2740
|
// src/extract/databases/prisma.ts
|
|
2616
2741
|
init_cjs_shims();
|
|
2617
|
-
var
|
|
2742
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2618
2743
|
async function parse3(serviceDir) {
|
|
2619
|
-
const schemaPath =
|
|
2744
|
+
const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2620
2745
|
const content = await readIfExists(schemaPath);
|
|
2621
2746
|
if (!content) return [];
|
|
2622
2747
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2748,10 +2873,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2748
2873
|
|
|
2749
2874
|
// src/extract/databases/ormconfig.ts
|
|
2750
2875
|
init_cjs_shims();
|
|
2751
|
-
var
|
|
2876
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2752
2877
|
async function parse6(serviceDir) {
|
|
2753
2878
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2754
|
-
const abs =
|
|
2879
|
+
const abs = import_node_path14.default.join(serviceDir, candidate);
|
|
2755
2880
|
if (!await exists(abs)) continue;
|
|
2756
2881
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2757
2882
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2811,9 +2936,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2811
2936
|
|
|
2812
2937
|
// src/extract/databases/sequelize.ts
|
|
2813
2938
|
init_cjs_shims();
|
|
2814
|
-
var
|
|
2939
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2815
2940
|
async function parse8(serviceDir) {
|
|
2816
|
-
const configPath =
|
|
2941
|
+
const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
|
|
2817
2942
|
if (!await exists(configPath)) return [];
|
|
2818
2943
|
const raw = await readJson(configPath);
|
|
2819
2944
|
const out = [];
|
|
@@ -2840,7 +2965,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2840
2965
|
|
|
2841
2966
|
// src/extract/databases/docker-compose.ts
|
|
2842
2967
|
init_cjs_shims();
|
|
2843
|
-
var
|
|
2968
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2844
2969
|
function portFromService(svc) {
|
|
2845
2970
|
for (const raw of svc.ports ?? []) {
|
|
2846
2971
|
const str = String(raw);
|
|
@@ -2867,7 +2992,7 @@ function databaseFromEnv(svc) {
|
|
|
2867
2992
|
}
|
|
2868
2993
|
async function parse9(serviceDir) {
|
|
2869
2994
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2870
|
-
const abs =
|
|
2995
|
+
const abs = import_node_path16.default.join(serviceDir, name);
|
|
2871
2996
|
if (!await exists(abs)) continue;
|
|
2872
2997
|
const raw = await readYaml(abs);
|
|
2873
2998
|
if (!raw?.services) return [];
|
|
@@ -3045,11 +3170,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3045
3170
|
target: dbNode.id,
|
|
3046
3171
|
type: import_types7.EdgeType.CONNECTS_TO,
|
|
3047
3172
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3173
|
+
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
3174
|
+
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3175
|
+
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3176
|
+
evidence: {
|
|
3177
|
+
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3178
|
+
}
|
|
3053
3179
|
};
|
|
3054
3180
|
if (!graph.hasEdge(edge.id)) {
|
|
3055
3181
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3075,15 +3201,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3075
3201
|
|
|
3076
3202
|
// src/extract/configs.ts
|
|
3077
3203
|
init_cjs_shims();
|
|
3078
|
-
var
|
|
3079
|
-
var
|
|
3204
|
+
var import_node_fs12 = require("fs");
|
|
3205
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3080
3206
|
var import_types8 = require("@neat.is/types");
|
|
3081
3207
|
async function walkConfigFiles(dir) {
|
|
3082
3208
|
const out = [];
|
|
3083
3209
|
async function walk(current) {
|
|
3084
|
-
const entries = await
|
|
3210
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
|
|
3085
3211
|
for (const entry of entries) {
|
|
3086
|
-
const full =
|
|
3212
|
+
const full = import_node_path18.default.join(current, entry.name);
|
|
3087
3213
|
if (entry.isDirectory()) {
|
|
3088
3214
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
3089
3215
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -3100,13 +3226,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3100
3226
|
for (const service of services) {
|
|
3101
3227
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3102
3228
|
for (const file of configFiles) {
|
|
3103
|
-
const relPath =
|
|
3229
|
+
const relPath = import_node_path18.default.relative(scanPath, file);
|
|
3104
3230
|
const node = {
|
|
3105
3231
|
id: (0, import_types8.configId)(relPath),
|
|
3106
3232
|
type: import_types8.NodeType.ConfigNode,
|
|
3107
|
-
name:
|
|
3233
|
+
name: import_node_path18.default.basename(file),
|
|
3108
3234
|
path: relPath,
|
|
3109
|
-
fileType: isConfigFile(
|
|
3235
|
+
fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
|
|
3110
3236
|
};
|
|
3111
3237
|
if (!graph.hasNode(node.id)) {
|
|
3112
3238
|
graph.addNode(node.id, node);
|
|
@@ -3118,7 +3244,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3118
3244
|
target: node.id,
|
|
3119
3245
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
3120
3246
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
3121
|
-
evidence: { file: relPath.split(
|
|
3247
|
+
evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
|
|
3122
3248
|
};
|
|
3123
3249
|
if (!graph.hasEdge(edge.id)) {
|
|
3124
3250
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3135,7 +3261,7 @@ var import_types14 = require("@neat.is/types");
|
|
|
3135
3261
|
|
|
3136
3262
|
// src/extract/calls/http.ts
|
|
3137
3263
|
init_cjs_shims();
|
|
3138
|
-
var
|
|
3264
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3139
3265
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3140
3266
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3141
3267
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -3143,17 +3269,17 @@ var import_types9 = require("@neat.is/types");
|
|
|
3143
3269
|
|
|
3144
3270
|
// src/extract/calls/shared.ts
|
|
3145
3271
|
init_cjs_shims();
|
|
3146
|
-
var
|
|
3147
|
-
var
|
|
3272
|
+
var import_node_fs13 = require("fs");
|
|
3273
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3148
3274
|
async function walkSourceFiles(dir) {
|
|
3149
3275
|
const out = [];
|
|
3150
3276
|
async function walk(current) {
|
|
3151
|
-
const entries = await
|
|
3277
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3152
3278
|
for (const entry of entries) {
|
|
3153
|
-
const full =
|
|
3279
|
+
const full = import_node_path19.default.join(current, entry.name);
|
|
3154
3280
|
if (entry.isDirectory()) {
|
|
3155
3281
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
3156
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3282
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3157
3283
|
out.push(full);
|
|
3158
3284
|
}
|
|
3159
3285
|
}
|
|
@@ -3166,7 +3292,7 @@ async function loadSourceFiles(dir) {
|
|
|
3166
3292
|
const out = [];
|
|
3167
3293
|
for (const p of paths) {
|
|
3168
3294
|
try {
|
|
3169
|
-
const content = await
|
|
3295
|
+
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3170
3296
|
out.push({ path: p, content });
|
|
3171
3297
|
} catch {
|
|
3172
3298
|
}
|
|
@@ -3185,8 +3311,27 @@ function snippet(text, line) {
|
|
|
3185
3311
|
|
|
3186
3312
|
// src/extract/calls/http.ts
|
|
3187
3313
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3314
|
+
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3315
|
+
function isInsideJsxExternalLink(node) {
|
|
3316
|
+
let cursor = node.parent;
|
|
3317
|
+
while (cursor) {
|
|
3318
|
+
if (cursor.type === "jsx_attribute") {
|
|
3319
|
+
let owner = cursor.parent;
|
|
3320
|
+
while (owner && owner.type !== "jsx_opening_element" && owner.type !== "jsx_self_closing_element") {
|
|
3321
|
+
owner = owner.parent;
|
|
3322
|
+
}
|
|
3323
|
+
if (!owner) return false;
|
|
3324
|
+
const tagNode = owner.namedChild(0);
|
|
3325
|
+
const tagName = tagNode?.text ?? "";
|
|
3326
|
+
const right = tagName.includes(".") ? tagName.split(".").pop() : tagName;
|
|
3327
|
+
return JSX_EXTERNAL_LINK_TAGS.has(right);
|
|
3328
|
+
}
|
|
3329
|
+
cursor = cursor.parent;
|
|
3330
|
+
}
|
|
3331
|
+
return false;
|
|
3332
|
+
}
|
|
3188
3333
|
function collectStringLiterals(node, out) {
|
|
3189
|
-
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push(node.text);
|
|
3334
|
+
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
|
|
3190
3335
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3191
3336
|
const child = node.namedChild(i);
|
|
3192
3337
|
if (child) collectStringLiterals(child, out);
|
|
@@ -3198,8 +3343,9 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3198
3343
|
collectStringLiterals(tree.rootNode, literals);
|
|
3199
3344
|
const targets = /* @__PURE__ */ new Set();
|
|
3200
3345
|
for (const lit of literals) {
|
|
3346
|
+
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3201
3347
|
for (const host of knownHosts) {
|
|
3202
|
-
if (lit.
|
|
3348
|
+
if (urlMatchesHost(lit.text, host)) {
|
|
3203
3349
|
targets.add(host);
|
|
3204
3350
|
}
|
|
3205
3351
|
}
|
|
@@ -3222,9 +3368,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3222
3368
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3223
3369
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3224
3370
|
for (const service of services) {
|
|
3225
|
-
knownHosts.add(
|
|
3371
|
+
knownHosts.add(import_node_path20.default.basename(service.dir));
|
|
3226
3372
|
knownHosts.add(service.pkg.name);
|
|
3227
|
-
hostToNodeId.set(
|
|
3373
|
+
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3228
3374
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3229
3375
|
}
|
|
3230
3376
|
let edgesAdded = 0;
|
|
@@ -3232,14 +3378,13 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3232
3378
|
const files = await loadSourceFiles(service.dir);
|
|
3233
3379
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3234
3380
|
for (const file of files) {
|
|
3235
|
-
|
|
3381
|
+
if (isTestPath(file.path)) continue;
|
|
3382
|
+
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3236
3383
|
let targets;
|
|
3237
3384
|
try {
|
|
3238
3385
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
3239
3386
|
} catch (err) {
|
|
3240
|
-
|
|
3241
|
-
`[neat] http call extraction skipped ${file.path}: ${err.message}`
|
|
3242
|
-
);
|
|
3387
|
+
recordExtractionError("http call extraction", file.path, err);
|
|
3243
3388
|
continue;
|
|
3244
3389
|
}
|
|
3245
3390
|
for (const t of targets) {
|
|
@@ -3260,7 +3405,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3260
3405
|
type: import_types9.EdgeType.CALLS,
|
|
3261
3406
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3262
3407
|
evidence: {
|
|
3263
|
-
file:
|
|
3408
|
+
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3264
3409
|
line,
|
|
3265
3410
|
snippet: snippet(fileContent, line)
|
|
3266
3411
|
}
|
|
@@ -3276,7 +3421,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3276
3421
|
|
|
3277
3422
|
// src/extract/calls/kafka.ts
|
|
3278
3423
|
init_cjs_shims();
|
|
3279
|
-
var
|
|
3424
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3280
3425
|
var import_types10 = require("@neat.is/types");
|
|
3281
3426
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3282
3427
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3303,7 +3448,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3303
3448
|
kind: "kafka-topic",
|
|
3304
3449
|
edgeType,
|
|
3305
3450
|
evidence: {
|
|
3306
|
-
file:
|
|
3451
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3307
3452
|
line,
|
|
3308
3453
|
snippet: snippet(file.content, line)
|
|
3309
3454
|
}
|
|
@@ -3316,7 +3461,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3316
3461
|
|
|
3317
3462
|
// src/extract/calls/redis.ts
|
|
3318
3463
|
init_cjs_shims();
|
|
3319
|
-
var
|
|
3464
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3320
3465
|
var import_types11 = require("@neat.is/types");
|
|
3321
3466
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3322
3467
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3335,7 +3480,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3335
3480
|
kind: "redis",
|
|
3336
3481
|
edgeType: "CALLS",
|
|
3337
3482
|
evidence: {
|
|
3338
|
-
file:
|
|
3483
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3339
3484
|
line,
|
|
3340
3485
|
snippet: snippet(file.content, line)
|
|
3341
3486
|
}
|
|
@@ -3346,7 +3491,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3346
3491
|
|
|
3347
3492
|
// src/extract/calls/aws.ts
|
|
3348
3493
|
init_cjs_shims();
|
|
3349
|
-
var
|
|
3494
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3350
3495
|
var import_types12 = require("@neat.is/types");
|
|
3351
3496
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3352
3497
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3376,7 +3521,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3376
3521
|
kind,
|
|
3377
3522
|
edgeType: "CALLS",
|
|
3378
3523
|
evidence: {
|
|
3379
|
-
file:
|
|
3524
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3380
3525
|
line,
|
|
3381
3526
|
snippet: snippet(file.content, line)
|
|
3382
3527
|
}
|
|
@@ -3401,16 +3546,42 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3401
3546
|
|
|
3402
3547
|
// src/extract/calls/grpc.ts
|
|
3403
3548
|
init_cjs_shims();
|
|
3404
|
-
var
|
|
3549
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3405
3550
|
var import_types13 = require("@neat.is/types");
|
|
3406
3551
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3552
|
+
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3553
|
+
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
3407
3554
|
function isLikelyAddress(value) {
|
|
3408
3555
|
if (!value) return false;
|
|
3409
3556
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
3410
3557
|
}
|
|
3558
|
+
function normaliseForMatch(s) {
|
|
3559
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3560
|
+
}
|
|
3561
|
+
function readImports(content) {
|
|
3562
|
+
const awsSdkSuffixes = /* @__PURE__ */ new Map();
|
|
3563
|
+
AWS_SDK_IMPORT_RE.lastIndex = 0;
|
|
3564
|
+
let m;
|
|
3565
|
+
while ((m = AWS_SDK_IMPORT_RE.exec(content)) !== null) {
|
|
3566
|
+
const raw = m[1];
|
|
3567
|
+
awsSdkSuffixes.set(normaliseForMatch(raw), raw);
|
|
3568
|
+
}
|
|
3569
|
+
return {
|
|
3570
|
+
awsSdkSuffixes,
|
|
3571
|
+
hasGrpcImport: GRPC_IMPORT_RE.test(content)
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
function classifyClient(symbol, ctx) {
|
|
3575
|
+
const key = normaliseForMatch(symbol);
|
|
3576
|
+
const awsRaw = ctx.awsSdkSuffixes.get(key);
|
|
3577
|
+
if (awsRaw) return { kind: `aws-${awsRaw}` };
|
|
3578
|
+
if (ctx.hasGrpcImport) return { kind: "grpc-service" };
|
|
3579
|
+
return null;
|
|
3580
|
+
}
|
|
3411
3581
|
function grpcEndpointsFromFile(file, serviceDir) {
|
|
3412
3582
|
const out = [];
|
|
3413
3583
|
const seen = /* @__PURE__ */ new Set();
|
|
3584
|
+
const ctx = readImports(file.content);
|
|
3414
3585
|
GRPC_CLIENT_RE.lastIndex = 0;
|
|
3415
3586
|
let m;
|
|
3416
3587
|
while ((m = GRPC_CLIENT_RE.exec(file.content)) !== null) {
|
|
@@ -3418,15 +3589,18 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3418
3589
|
const addr = m[2]?.trim();
|
|
3419
3590
|
const name = isLikelyAddress(addr) ? addr : symbol;
|
|
3420
3591
|
if (seen.has(name)) continue;
|
|
3592
|
+
const classified = classifyClient(symbol, ctx);
|
|
3593
|
+
if (!classified) continue;
|
|
3421
3594
|
seen.add(name);
|
|
3595
|
+
const { kind } = classified;
|
|
3422
3596
|
const line = lineOf(file.content, m[0]);
|
|
3423
3597
|
out.push({
|
|
3424
|
-
infraId: (0, import_types13.infraId)(
|
|
3598
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3425
3599
|
name,
|
|
3426
|
-
kind
|
|
3600
|
+
kind,
|
|
3427
3601
|
edgeType: "CALLS",
|
|
3428
3602
|
evidence: {
|
|
3429
|
-
file:
|
|
3603
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3430
3604
|
line,
|
|
3431
3605
|
snippet: snippet(file.content, line)
|
|
3432
3606
|
}
|
|
@@ -3446,6 +3620,9 @@ function edgeTypeFromEndpoint(ep) {
|
|
|
3446
3620
|
return import_types14.EdgeType.CALLS;
|
|
3447
3621
|
}
|
|
3448
3622
|
}
|
|
3623
|
+
function isAwsKind(kind) {
|
|
3624
|
+
return kind.startsWith("aws-") || kind.startsWith("s3") || kind.startsWith("dynamodb");
|
|
3625
|
+
}
|
|
3449
3626
|
async function addExternalEndpointEdges(graph, services) {
|
|
3450
3627
|
let nodesAdded = 0;
|
|
3451
3628
|
let edgesAdded = 0;
|
|
@@ -3453,10 +3630,13 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3453
3630
|
const files = await loadSourceFiles(service.dir);
|
|
3454
3631
|
const endpoints = [];
|
|
3455
3632
|
for (const file of files) {
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
endpoints.push(...
|
|
3633
|
+
if (isTestPath(file.path)) continue;
|
|
3634
|
+
const masked = maskCommentsInSource(file.content);
|
|
3635
|
+
const maskedFile = { path: file.path, content: masked };
|
|
3636
|
+
endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
|
|
3637
|
+
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
3638
|
+
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
3639
|
+
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
3460
3640
|
}
|
|
3461
3641
|
if (endpoints.length === 0) continue;
|
|
3462
3642
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
@@ -3466,7 +3646,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3466
3646
|
id: ep.infraId,
|
|
3467
3647
|
type: import_types14.NodeType.InfraNode,
|
|
3468
3648
|
name: ep.name,
|
|
3469
|
-
|
|
3649
|
+
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3650
|
+
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
3651
|
+
// bucket / table kinds from aws.ts.
|
|
3652
|
+
provider: isAwsKind(ep.kind) ? "aws" : "self",
|
|
3470
3653
|
kind: ep.kind
|
|
3471
3654
|
};
|
|
3472
3655
|
graph.addNode(node.id, node);
|
|
@@ -3506,7 +3689,7 @@ init_cjs_shims();
|
|
|
3506
3689
|
|
|
3507
3690
|
// src/extract/infra/docker-compose.ts
|
|
3508
3691
|
init_cjs_shims();
|
|
3509
|
-
var
|
|
3692
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3510
3693
|
var import_types16 = require("@neat.is/types");
|
|
3511
3694
|
|
|
3512
3695
|
// src/extract/infra/shared.ts
|
|
@@ -3544,7 +3727,7 @@ function dependsOnList(value) {
|
|
|
3544
3727
|
}
|
|
3545
3728
|
function serviceNameToServiceNode(name, services) {
|
|
3546
3729
|
for (const s of services) {
|
|
3547
|
-
if (s.node.name === name ||
|
|
3730
|
+
if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
|
|
3548
3731
|
}
|
|
3549
3732
|
return null;
|
|
3550
3733
|
}
|
|
@@ -3553,7 +3736,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3553
3736
|
let edgesAdded = 0;
|
|
3554
3737
|
let composePath = null;
|
|
3555
3738
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3556
|
-
const abs =
|
|
3739
|
+
const abs = import_node_path25.default.join(scanPath, name);
|
|
3557
3740
|
if (await exists(abs)) {
|
|
3558
3741
|
composePath = abs;
|
|
3559
3742
|
break;
|
|
@@ -3564,13 +3747,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3564
3747
|
try {
|
|
3565
3748
|
compose = await readYaml(composePath);
|
|
3566
3749
|
} catch (err) {
|
|
3567
|
-
|
|
3568
|
-
|
|
3750
|
+
recordExtractionError(
|
|
3751
|
+
"infra docker-compose",
|
|
3752
|
+
import_node_path25.default.relative(scanPath, composePath),
|
|
3753
|
+
err
|
|
3569
3754
|
);
|
|
3570
3755
|
return { nodesAdded, edgesAdded };
|
|
3571
3756
|
}
|
|
3572
3757
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3573
|
-
const evidenceFile =
|
|
3758
|
+
const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
|
|
3574
3759
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3575
3760
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3576
3761
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3611,8 +3796,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3611
3796
|
|
|
3612
3797
|
// src/extract/infra/dockerfile.ts
|
|
3613
3798
|
init_cjs_shims();
|
|
3614
|
-
var
|
|
3615
|
-
var
|
|
3799
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3800
|
+
var import_node_fs14 = require("fs");
|
|
3616
3801
|
var import_types17 = require("@neat.is/types");
|
|
3617
3802
|
function runtimeImage(content) {
|
|
3618
3803
|
const lines = content.split("\n");
|
|
@@ -3632,14 +3817,16 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3632
3817
|
let nodesAdded = 0;
|
|
3633
3818
|
let edgesAdded = 0;
|
|
3634
3819
|
for (const service of services) {
|
|
3635
|
-
const dockerfilePath =
|
|
3820
|
+
const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
|
|
3636
3821
|
if (!await exists(dockerfilePath)) continue;
|
|
3637
3822
|
let content;
|
|
3638
3823
|
try {
|
|
3639
|
-
content = await
|
|
3824
|
+
content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
|
|
3640
3825
|
} catch (err) {
|
|
3641
|
-
|
|
3642
|
-
|
|
3826
|
+
recordExtractionError(
|
|
3827
|
+
"infra dockerfile",
|
|
3828
|
+
import_node_path26.default.relative(scanPath, dockerfilePath),
|
|
3829
|
+
err
|
|
3643
3830
|
);
|
|
3644
3831
|
continue;
|
|
3645
3832
|
}
|
|
@@ -3659,7 +3846,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3659
3846
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3660
3847
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3661
3848
|
evidence: {
|
|
3662
|
-
file:
|
|
3849
|
+
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
3663
3850
|
}
|
|
3664
3851
|
};
|
|
3665
3852
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3671,19 +3858,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3671
3858
|
|
|
3672
3859
|
// src/extract/infra/terraform.ts
|
|
3673
3860
|
init_cjs_shims();
|
|
3674
|
-
var
|
|
3675
|
-
var
|
|
3861
|
+
var import_node_fs15 = require("fs");
|
|
3862
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3676
3863
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3677
3864
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3678
3865
|
if (depth > max) return [];
|
|
3679
3866
|
const out = [];
|
|
3680
|
-
const entries = await
|
|
3867
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3681
3868
|
for (const entry of entries) {
|
|
3682
3869
|
if (entry.isDirectory()) {
|
|
3683
3870
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3684
|
-
out.push(...await walkTfFiles(
|
|
3871
|
+
out.push(...await walkTfFiles(import_node_path27.default.join(start, entry.name), depth + 1, max));
|
|
3685
3872
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3686
|
-
out.push(
|
|
3873
|
+
out.push(import_node_path27.default.join(start, entry.name));
|
|
3687
3874
|
}
|
|
3688
3875
|
}
|
|
3689
3876
|
return out;
|
|
@@ -3692,7 +3879,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3692
3879
|
let nodesAdded = 0;
|
|
3693
3880
|
const files = await walkTfFiles(scanPath);
|
|
3694
3881
|
for (const file of files) {
|
|
3695
|
-
const content = await
|
|
3882
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3696
3883
|
RESOURCE_RE.lastIndex = 0;
|
|
3697
3884
|
let m;
|
|
3698
3885
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3710,8 +3897,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3710
3897
|
|
|
3711
3898
|
// src/extract/infra/k8s.ts
|
|
3712
3899
|
init_cjs_shims();
|
|
3713
|
-
var
|
|
3714
|
-
var
|
|
3900
|
+
var import_node_fs16 = require("fs");
|
|
3901
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3715
3902
|
var import_yaml3 = require("yaml");
|
|
3716
3903
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3717
3904
|
Service: "k8s-service",
|
|
@@ -3725,13 +3912,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3725
3912
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3726
3913
|
if (depth > max) return [];
|
|
3727
3914
|
const out = [];
|
|
3728
|
-
const entries = await
|
|
3915
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3729
3916
|
for (const entry of entries) {
|
|
3730
3917
|
if (entry.isDirectory()) {
|
|
3731
3918
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3732
|
-
out.push(...await walkYamlFiles2(
|
|
3733
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3734
|
-
out.push(
|
|
3919
|
+
out.push(...await walkYamlFiles2(import_node_path28.default.join(start, entry.name), depth + 1, max));
|
|
3920
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry.name))) {
|
|
3921
|
+
out.push(import_node_path28.default.join(start, entry.name));
|
|
3735
3922
|
}
|
|
3736
3923
|
}
|
|
3737
3924
|
return out;
|
|
@@ -3740,7 +3927,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3740
3927
|
let nodesAdded = 0;
|
|
3741
3928
|
const files = await walkYamlFiles2(scanPath);
|
|
3742
3929
|
for (const file of files) {
|
|
3743
|
-
const content = await
|
|
3930
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
3744
3931
|
let docs;
|
|
3745
3932
|
try {
|
|
3746
3933
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3774,9 +3961,34 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3774
3961
|
};
|
|
3775
3962
|
}
|
|
3776
3963
|
|
|
3964
|
+
// src/extract/retire.ts
|
|
3965
|
+
init_cjs_shims();
|
|
3966
|
+
var import_node_fs17 = require("fs");
|
|
3967
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
3968
|
+
var import_types18 = require("@neat.is/types");
|
|
3969
|
+
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
3970
|
+
const toDrop = [];
|
|
3971
|
+
const bases = [scanPath, ...serviceDirs];
|
|
3972
|
+
graph.forEachEdge((id, attrs) => {
|
|
3973
|
+
const edge = attrs;
|
|
3974
|
+
if (edge.provenance !== import_types18.Provenance.EXTRACTED) return;
|
|
3975
|
+
const evidenceFile = edge.evidence?.file;
|
|
3976
|
+
if (!evidenceFile) return;
|
|
3977
|
+
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
3978
|
+
if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
|
|
3982
|
+
if (!found) toDrop.push(id);
|
|
3983
|
+
});
|
|
3984
|
+
for (const id of toDrop) graph.dropEdge(id);
|
|
3985
|
+
return toDrop.length;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3777
3988
|
// src/extract/index.ts
|
|
3778
3989
|
async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
3779
3990
|
await ensureCompatLoaded();
|
|
3991
|
+
drainExtractionErrors();
|
|
3780
3992
|
const services = await discoverServices(scanPath);
|
|
3781
3993
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
3782
3994
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -3784,12 +3996,30 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3784
3996
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
3785
3997
|
const phase4 = await addCallEdges(graph, services);
|
|
3786
3998
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
3999
|
+
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
4000
|
+
graph,
|
|
4001
|
+
scanPath,
|
|
4002
|
+
services.map((s) => s.dir)
|
|
4003
|
+
);
|
|
3787
4004
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
3788
4005
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
4006
|
+
const errorEntries = drainExtractionErrors();
|
|
4007
|
+
if (opts.errorsPath && errorEntries.length > 0) {
|
|
4008
|
+
try {
|
|
4009
|
+
await writeExtractionErrors(errorEntries, opts.errorsPath);
|
|
4010
|
+
} catch (err) {
|
|
4011
|
+
console.warn(
|
|
4012
|
+
`[neat] failed to write extraction errors to ${opts.errorsPath}: ${err.message}`
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3789
4016
|
const result = {
|
|
3790
4017
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3791
4018
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3792
|
-
frontiersPromoted
|
|
4019
|
+
frontiersPromoted,
|
|
4020
|
+
extractionErrors: errorEntries.length,
|
|
4021
|
+
errorEntries,
|
|
4022
|
+
ghostsRetired
|
|
3793
4023
|
};
|
|
3794
4024
|
emitNeatEvent({
|
|
3795
4025
|
type: "extraction-complete",
|
|
@@ -3806,8 +4036,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3806
4036
|
|
|
3807
4037
|
// src/persist.ts
|
|
3808
4038
|
init_cjs_shims();
|
|
3809
|
-
var
|
|
3810
|
-
var
|
|
4039
|
+
var import_node_fs18 = require("fs");
|
|
4040
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
3811
4041
|
var SCHEMA_VERSION = 2;
|
|
3812
4042
|
function migrateV1ToV2(payload) {
|
|
3813
4043
|
const nodes = payload.graph.nodes;
|
|
@@ -3821,7 +4051,7 @@ function migrateV1ToV2(payload) {
|
|
|
3821
4051
|
return { ...payload, schemaVersion: 2 };
|
|
3822
4052
|
}
|
|
3823
4053
|
async function ensureDir(filePath) {
|
|
3824
|
-
await
|
|
4054
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
|
|
3825
4055
|
}
|
|
3826
4056
|
async function saveGraphToDisk(graph, outPath) {
|
|
3827
4057
|
await ensureDir(outPath);
|
|
@@ -3831,13 +4061,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
3831
4061
|
graph: graph.export()
|
|
3832
4062
|
};
|
|
3833
4063
|
const tmp = `${outPath}.tmp`;
|
|
3834
|
-
await
|
|
3835
|
-
await
|
|
4064
|
+
await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4065
|
+
await import_node_fs18.promises.rename(tmp, outPath);
|
|
3836
4066
|
}
|
|
3837
4067
|
async function loadGraphFromDisk(graph, outPath) {
|
|
3838
4068
|
let raw;
|
|
3839
4069
|
try {
|
|
3840
|
-
raw = await
|
|
4070
|
+
raw = await import_node_fs18.promises.readFile(outPath, "utf8");
|
|
3841
4071
|
} catch (err) {
|
|
3842
4072
|
if (err.code === "ENOENT") return;
|
|
3843
4073
|
throw err;
|
|
@@ -3892,11 +4122,11 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
3892
4122
|
init_cjs_shims();
|
|
3893
4123
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
3894
4124
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
3895
|
-
var
|
|
4125
|
+
var import_types21 = require("@neat.is/types");
|
|
3896
4126
|
|
|
3897
4127
|
// src/divergences.ts
|
|
3898
4128
|
init_cjs_shims();
|
|
3899
|
-
var
|
|
4129
|
+
var import_types19 = require("@neat.is/types");
|
|
3900
4130
|
function bucketKey(source, target, type) {
|
|
3901
4131
|
return `${type}|${source}|${target}`;
|
|
3902
4132
|
}
|
|
@@ -3904,25 +4134,25 @@ function bucketEdges(graph) {
|
|
|
3904
4134
|
const buckets = /* @__PURE__ */ new Map();
|
|
3905
4135
|
graph.forEachEdge((id, attrs) => {
|
|
3906
4136
|
const e = attrs;
|
|
3907
|
-
const parsed = (0,
|
|
4137
|
+
const parsed = (0, import_types19.parseEdgeId)(id);
|
|
3908
4138
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
3909
4139
|
const key = bucketKey(e.source, e.target, e.type);
|
|
3910
4140
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3911
4141
|
switch (provenance) {
|
|
3912
|
-
case
|
|
4142
|
+
case import_types19.Provenance.EXTRACTED:
|
|
3913
4143
|
cur.extracted = e;
|
|
3914
4144
|
break;
|
|
3915
|
-
case
|
|
4145
|
+
case import_types19.Provenance.OBSERVED:
|
|
3916
4146
|
cur.observed = e;
|
|
3917
4147
|
break;
|
|
3918
|
-
case
|
|
4148
|
+
case import_types19.Provenance.INFERRED:
|
|
3919
4149
|
cur.inferred = e;
|
|
3920
4150
|
break;
|
|
3921
|
-
case
|
|
4151
|
+
case import_types19.Provenance.FRONTIER:
|
|
3922
4152
|
cur.frontier = e;
|
|
3923
4153
|
break;
|
|
3924
4154
|
default:
|
|
3925
|
-
if (e.provenance ===
|
|
4155
|
+
if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
|
|
3926
4156
|
}
|
|
3927
4157
|
buckets.set(key, cur);
|
|
3928
4158
|
});
|
|
@@ -3931,13 +4161,13 @@ function bucketEdges(graph) {
|
|
|
3931
4161
|
function nodeIsFrontier(graph, nodeId) {
|
|
3932
4162
|
if (!graph.hasNode(nodeId)) return false;
|
|
3933
4163
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
3934
|
-
return attrs.type ===
|
|
4164
|
+
return attrs.type === import_types19.NodeType.FrontierNode;
|
|
3935
4165
|
}
|
|
3936
4166
|
function hasAnyObservedFromSource(graph, sourceId) {
|
|
3937
4167
|
if (!graph.hasNode(sourceId)) return false;
|
|
3938
4168
|
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
3939
4169
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3940
|
-
if (e.provenance ===
|
|
4170
|
+
if (e.provenance === import_types19.Provenance.OBSERVED) return true;
|
|
3941
4171
|
}
|
|
3942
4172
|
return false;
|
|
3943
4173
|
}
|
|
@@ -3998,7 +4228,7 @@ function declaredHostFor(svc) {
|
|
|
3998
4228
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
3999
4229
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4000
4230
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4001
|
-
if (e.type ===
|
|
4231
|
+
if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
|
|
4002
4232
|
return true;
|
|
4003
4233
|
}
|
|
4004
4234
|
}
|
|
@@ -4011,10 +4241,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4011
4241
|
const out = [];
|
|
4012
4242
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4013
4243
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4014
|
-
if (edge.type !==
|
|
4015
|
-
if (edge.provenance !==
|
|
4244
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4245
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
4016
4246
|
const target = graph.getNodeAttributes(edge.target);
|
|
4017
|
-
if (target.type !==
|
|
4247
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
4018
4248
|
const observedHost = target.host?.trim();
|
|
4019
4249
|
if (!observedHost) continue;
|
|
4020
4250
|
if (observedHost === declaredHost) continue;
|
|
@@ -4036,10 +4266,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4036
4266
|
const deps = svc.dependencies ?? {};
|
|
4037
4267
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4038
4268
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4039
|
-
if (edge.type !==
|
|
4040
|
-
if (edge.provenance !==
|
|
4269
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4270
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
4041
4271
|
const target = graph.getNodeAttributes(edge.target);
|
|
4042
|
-
if (target.type !==
|
|
4272
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
4043
4273
|
for (const pair of compatPairs()) {
|
|
4044
4274
|
if (pair.engine !== target.engine) continue;
|
|
4045
4275
|
const declared = deps[pair.driver];
|
|
@@ -4100,7 +4330,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4100
4330
|
}
|
|
4101
4331
|
graph.forEachNode((nodeId, attrs) => {
|
|
4102
4332
|
const n = attrs;
|
|
4103
|
-
if (n.type !==
|
|
4333
|
+
if (n.type !== import_types19.NodeType.ServiceNode) return;
|
|
4104
4334
|
const svc = n;
|
|
4105
4335
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4106
4336
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4124,7 +4354,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4124
4354
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4125
4355
|
return a.target.localeCompare(b.target);
|
|
4126
4356
|
});
|
|
4127
|
-
return
|
|
4357
|
+
return import_types19.DivergenceResultSchema.parse({
|
|
4128
4358
|
divergences: filtered,
|
|
4129
4359
|
totalAffected: filtered.length,
|
|
4130
4360
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4133,7 +4363,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4133
4363
|
|
|
4134
4364
|
// src/diff.ts
|
|
4135
4365
|
init_cjs_shims();
|
|
4136
|
-
var
|
|
4366
|
+
var import_node_fs19 = require("fs");
|
|
4137
4367
|
async function loadSnapshotForDiff(target) {
|
|
4138
4368
|
if (/^https?:\/\//i.test(target)) {
|
|
4139
4369
|
const res = await fetch(target);
|
|
@@ -4142,7 +4372,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4142
4372
|
}
|
|
4143
4373
|
return await res.json();
|
|
4144
4374
|
}
|
|
4145
|
-
const raw = await
|
|
4375
|
+
const raw = await import_node_fs19.promises.readFile(target, "utf8");
|
|
4146
4376
|
return JSON.parse(raw);
|
|
4147
4377
|
}
|
|
4148
4378
|
function indexEntries(entries) {
|
|
@@ -4210,23 +4440,23 @@ function canonicalJson(value) {
|
|
|
4210
4440
|
|
|
4211
4441
|
// src/projects.ts
|
|
4212
4442
|
init_cjs_shims();
|
|
4213
|
-
var
|
|
4443
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4214
4444
|
function pathsForProject(project, baseDir) {
|
|
4215
4445
|
if (project === DEFAULT_PROJECT) {
|
|
4216
4446
|
return {
|
|
4217
|
-
snapshotPath:
|
|
4218
|
-
errorsPath:
|
|
4219
|
-
staleEventsPath:
|
|
4220
|
-
embeddingsCachePath:
|
|
4221
|
-
policyViolationsPath:
|
|
4447
|
+
snapshotPath: import_node_path31.default.join(baseDir, "graph.json"),
|
|
4448
|
+
errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
|
|
4449
|
+
staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
|
|
4450
|
+
embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
|
|
4451
|
+
policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
|
|
4222
4452
|
};
|
|
4223
4453
|
}
|
|
4224
4454
|
return {
|
|
4225
|
-
snapshotPath:
|
|
4226
|
-
errorsPath:
|
|
4227
|
-
staleEventsPath:
|
|
4228
|
-
embeddingsCachePath:
|
|
4229
|
-
policyViolationsPath:
|
|
4455
|
+
snapshotPath: import_node_path31.default.join(baseDir, `${project}.json`),
|
|
4456
|
+
errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
|
|
4457
|
+
staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4458
|
+
embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
|
|
4459
|
+
policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4230
4460
|
};
|
|
4231
4461
|
}
|
|
4232
4462
|
var Projects = class {
|
|
@@ -4262,49 +4492,49 @@ var Projects = class {
|
|
|
4262
4492
|
|
|
4263
4493
|
// src/registry.ts
|
|
4264
4494
|
init_cjs_shims();
|
|
4265
|
-
var
|
|
4495
|
+
var import_node_fs20 = require("fs");
|
|
4266
4496
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4267
|
-
var
|
|
4268
|
-
var
|
|
4497
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4498
|
+
var import_types20 = require("@neat.is/types");
|
|
4269
4499
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4270
4500
|
var LOCK_RETRY_MS = 50;
|
|
4271
4501
|
function neatHome() {
|
|
4272
4502
|
const override = process.env.NEAT_HOME;
|
|
4273
|
-
if (override && override.length > 0) return
|
|
4274
|
-
return
|
|
4503
|
+
if (override && override.length > 0) return import_node_path32.default.resolve(override);
|
|
4504
|
+
return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
|
|
4275
4505
|
}
|
|
4276
4506
|
function registryPath() {
|
|
4277
|
-
return
|
|
4507
|
+
return import_node_path32.default.join(neatHome(), "projects.json");
|
|
4278
4508
|
}
|
|
4279
4509
|
function registryLockPath() {
|
|
4280
|
-
return
|
|
4510
|
+
return import_node_path32.default.join(neatHome(), "projects.json.lock");
|
|
4281
4511
|
}
|
|
4282
4512
|
async function normalizeProjectPath(input) {
|
|
4283
|
-
const resolved =
|
|
4513
|
+
const resolved = import_node_path32.default.resolve(input);
|
|
4284
4514
|
try {
|
|
4285
|
-
return await
|
|
4515
|
+
return await import_node_fs20.promises.realpath(resolved);
|
|
4286
4516
|
} catch {
|
|
4287
4517
|
return resolved;
|
|
4288
4518
|
}
|
|
4289
4519
|
}
|
|
4290
4520
|
async function writeAtomically(target, contents) {
|
|
4291
|
-
await
|
|
4521
|
+
await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(target), { recursive: true });
|
|
4292
4522
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4293
|
-
const fd = await
|
|
4523
|
+
const fd = await import_node_fs20.promises.open(tmp, "w");
|
|
4294
4524
|
try {
|
|
4295
4525
|
await fd.writeFile(contents, "utf8");
|
|
4296
4526
|
await fd.sync();
|
|
4297
4527
|
} finally {
|
|
4298
4528
|
await fd.close();
|
|
4299
4529
|
}
|
|
4300
|
-
await
|
|
4530
|
+
await import_node_fs20.promises.rename(tmp, target);
|
|
4301
4531
|
}
|
|
4302
4532
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
4303
4533
|
const deadline = Date.now() + timeoutMs;
|
|
4304
|
-
await
|
|
4534
|
+
await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(lockPath), { recursive: true });
|
|
4305
4535
|
while (true) {
|
|
4306
4536
|
try {
|
|
4307
|
-
const fd = await
|
|
4537
|
+
const fd = await import_node_fs20.promises.open(lockPath, "wx");
|
|
4308
4538
|
await fd.close();
|
|
4309
4539
|
return;
|
|
4310
4540
|
} catch (err) {
|
|
@@ -4320,7 +4550,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
4320
4550
|
}
|
|
4321
4551
|
}
|
|
4322
4552
|
async function releaseLock(lockPath) {
|
|
4323
|
-
await
|
|
4553
|
+
await import_node_fs20.promises.unlink(lockPath).catch(() => {
|
|
4324
4554
|
});
|
|
4325
4555
|
}
|
|
4326
4556
|
async function withLock(fn) {
|
|
@@ -4336,7 +4566,7 @@ async function readRegistry() {
|
|
|
4336
4566
|
const file = registryPath();
|
|
4337
4567
|
let raw;
|
|
4338
4568
|
try {
|
|
4339
|
-
raw = await
|
|
4569
|
+
raw = await import_node_fs20.promises.readFile(file, "utf8");
|
|
4340
4570
|
} catch (err) {
|
|
4341
4571
|
if (err.code === "ENOENT") {
|
|
4342
4572
|
return { version: 1, projects: [] };
|
|
@@ -4344,10 +4574,10 @@ async function readRegistry() {
|
|
|
4344
4574
|
throw err;
|
|
4345
4575
|
}
|
|
4346
4576
|
const parsed = JSON.parse(raw);
|
|
4347
|
-
return
|
|
4577
|
+
return import_types20.RegistryFileSchema.parse(parsed);
|
|
4348
4578
|
}
|
|
4349
4579
|
async function writeRegistry(reg) {
|
|
4350
|
-
const validated =
|
|
4580
|
+
const validated = import_types20.RegistryFileSchema.parse(reg);
|
|
4351
4581
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4352
4582
|
}
|
|
4353
4583
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -4607,11 +4837,11 @@ function registerRoutes(scope, ctx) {
|
|
|
4607
4837
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4608
4838
|
const parsed = [];
|
|
4609
4839
|
for (const c of candidates) {
|
|
4610
|
-
const r =
|
|
4840
|
+
const r = import_types21.DivergenceTypeSchema.safeParse(c);
|
|
4611
4841
|
if (!r.success) {
|
|
4612
4842
|
return reply.code(400).send({
|
|
4613
4843
|
error: `unknown divergence type "${c}"`,
|
|
4614
|
-
allowed:
|
|
4844
|
+
allowed: import_types21.DivergenceTypeSchema.options
|
|
4615
4845
|
});
|
|
4616
4846
|
}
|
|
4617
4847
|
parsed.push(r.data);
|
|
@@ -4795,7 +5025,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4795
5025
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4796
5026
|
let violations = await log.readAll();
|
|
4797
5027
|
if (req.query.severity) {
|
|
4798
|
-
const sev =
|
|
5028
|
+
const sev = import_types21.PolicySeveritySchema.safeParse(req.query.severity);
|
|
4799
5029
|
if (!sev.success) {
|
|
4800
5030
|
return reply.code(400).send({
|
|
4801
5031
|
error: "invalid severity",
|
|
@@ -4812,7 +5042,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4812
5042
|
scope.post("/policies/check", async (req, reply) => {
|
|
4813
5043
|
const proj = resolveProject(registry, req, reply);
|
|
4814
5044
|
if (!proj) return;
|
|
4815
|
-
const parsed =
|
|
5045
|
+
const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
4816
5046
|
if (!parsed.success) {
|
|
4817
5047
|
return reply.code(400).send({
|
|
4818
5048
|
error: "invalid /policies/check body",
|
|
@@ -4916,15 +5146,15 @@ init_otel_grpc();
|
|
|
4916
5146
|
|
|
4917
5147
|
// src/daemon.ts
|
|
4918
5148
|
init_cjs_shims();
|
|
4919
|
-
var
|
|
4920
|
-
var
|
|
5149
|
+
var import_node_fs21 = require("fs");
|
|
5150
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
4921
5151
|
init_otel();
|
|
4922
5152
|
function neatHomeFor(opts) {
|
|
4923
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
5153
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path35.default.resolve(opts.neatHome);
|
|
4924
5154
|
const env = process.env.NEAT_HOME;
|
|
4925
|
-
if (env && env.length > 0) return
|
|
5155
|
+
if (env && env.length > 0) return import_node_path35.default.resolve(env);
|
|
4926
5156
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4927
|
-
return
|
|
5157
|
+
return import_node_path35.default.join(home, ".neat");
|
|
4928
5158
|
}
|
|
4929
5159
|
function routeSpanToProject(serviceName, projects) {
|
|
4930
5160
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -4937,9 +5167,9 @@ function routeSpanToProject(serviceName, projects) {
|
|
|
4937
5167
|
return DEFAULT_PROJECT;
|
|
4938
5168
|
}
|
|
4939
5169
|
async function bootstrapProject(entry) {
|
|
4940
|
-
const paths = pathsForProject(entry.name,
|
|
5170
|
+
const paths = pathsForProject(entry.name, import_node_path35.default.join(entry.path, "neat-out"));
|
|
4941
5171
|
try {
|
|
4942
|
-
const stat = await
|
|
5172
|
+
const stat = await import_node_fs21.promises.stat(entry.path);
|
|
4943
5173
|
if (!stat.isDirectory()) {
|
|
4944
5174
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
4945
5175
|
}
|
|
@@ -5004,13 +5234,13 @@ async function startDaemon(opts = {}) {
|
|
|
5004
5234
|
const home = neatHomeFor(opts);
|
|
5005
5235
|
const regPath = registryPath();
|
|
5006
5236
|
try {
|
|
5007
|
-
await
|
|
5237
|
+
await import_node_fs21.promises.access(regPath);
|
|
5008
5238
|
} catch {
|
|
5009
5239
|
throw new Error(
|
|
5010
5240
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5011
5241
|
);
|
|
5012
5242
|
}
|
|
5013
|
-
const pidPath =
|
|
5243
|
+
const pidPath = import_node_path35.default.join(home, "neatd.pid");
|
|
5014
5244
|
await writeAtomically(pidPath, `${process.pid}
|
|
5015
5245
|
`);
|
|
5016
5246
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -5079,7 +5309,7 @@ async function startDaemon(opts = {}) {
|
|
|
5079
5309
|
}
|
|
5080
5310
|
if (restApp) await restApp.close().catch(() => {
|
|
5081
5311
|
});
|
|
5082
|
-
await
|
|
5312
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
5083
5313
|
});
|
|
5084
5314
|
throw new Error(
|
|
5085
5315
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -5124,7 +5354,7 @@ async function startDaemon(opts = {}) {
|
|
|
5124
5354
|
});
|
|
5125
5355
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
5126
5356
|
});
|
|
5127
|
-
await
|
|
5357
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
5128
5358
|
});
|
|
5129
5359
|
throw new Error(
|
|
5130
5360
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -5164,7 +5394,7 @@ async function startDaemon(opts = {}) {
|
|
|
5164
5394
|
} catch {
|
|
5165
5395
|
}
|
|
5166
5396
|
}
|
|
5167
|
-
await
|
|
5397
|
+
await import_node_fs21.promises.unlink(pidPath).catch(() => {
|
|
5168
5398
|
});
|
|
5169
5399
|
};
|
|
5170
5400
|
return { slots, reload, stop, pidPath, restAddress, otlpAddress };
|