@neat.is/core 0.3.2 → 0.3.4
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-FIXKIYNF.js → chunk-33ZZ2ZID.js} +606 -240
- package/dist/chunk-33ZZ2ZID.js.map +1 -0
- package/dist/{chunk-66Z5IEFY.js → chunk-T3A2GK3X.js} +2 -2
- package/dist/cli.cjs +674 -337
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +26 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +565 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +572 -256
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +535 -219
- 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-T3A2GK3X.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -101,8 +101,8 @@ function reshapeGrpcRequest(req) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
function resolveProtoRoot() {
|
|
104
|
-
const here =
|
|
105
|
-
return
|
|
104
|
+
const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
105
|
+
return import_node_path34.default.resolve(here, "..", "proto");
|
|
106
106
|
}
|
|
107
107
|
function loadTraceService() {
|
|
108
108
|
const protoRoot = resolveProtoRoot();
|
|
@@ -157,13 +157,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
157
157
|
})
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
|
-
var import_node_url,
|
|
160
|
+
var import_node_url, import_node_path34, grpc, protoLoader;
|
|
161
161
|
var init_otel_grpc = __esm({
|
|
162
162
|
"src/otel-grpc.ts"() {
|
|
163
163
|
"use strict";
|
|
164
164
|
init_cjs_shims();
|
|
165
165
|
import_node_url = require("url");
|
|
166
|
-
|
|
166
|
+
import_node_path34 = __toESM(require("path"), 1);
|
|
167
167
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
168
168
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
169
169
|
init_otel();
|
|
@@ -260,10 +260,10 @@ function parseOtlpRequest(body) {
|
|
|
260
260
|
}
|
|
261
261
|
function loadProtobufDecoder() {
|
|
262
262
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
263
|
-
const here =
|
|
264
|
-
const protoRoot =
|
|
263
|
+
const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
264
|
+
const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
|
|
265
265
|
const root = new import_protobufjs.default.Root();
|
|
266
|
-
root.resolvePath = (_origin, target) =>
|
|
266
|
+
root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
|
|
267
267
|
root.loadSync(
|
|
268
268
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
269
269
|
{ keepCase: true }
|
|
@@ -355,12 +355,12 @@ async function buildOtelReceiver(opts) {
|
|
|
355
355
|
};
|
|
356
356
|
return decorated;
|
|
357
357
|
}
|
|
358
|
-
var
|
|
358
|
+
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
359
359
|
var init_otel = __esm({
|
|
360
360
|
"src/otel.ts"() {
|
|
361
361
|
"use strict";
|
|
362
362
|
init_cjs_shims();
|
|
363
|
-
|
|
363
|
+
import_node_path35 = __toESM(require("path"), 1);
|
|
364
364
|
import_node_url2 = require("url");
|
|
365
365
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
366
366
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -380,8 +380,8 @@ __export(cli_exports, {
|
|
|
380
380
|
});
|
|
381
381
|
module.exports = __toCommonJS(cli_exports);
|
|
382
382
|
init_cjs_shims();
|
|
383
|
-
var
|
|
384
|
-
var
|
|
383
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
384
|
+
var import_node_fs25 = require("fs");
|
|
385
385
|
|
|
386
386
|
// src/graph.ts
|
|
387
387
|
init_cjs_shims();
|
|
@@ -887,19 +887,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
887
887
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
888
888
|
let best = { path: [start], edges: [] };
|
|
889
889
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
890
|
-
function step(node,
|
|
891
|
-
if (
|
|
892
|
-
best = { path: [...
|
|
890
|
+
function step(node, path41, edges) {
|
|
891
|
+
if (path41.length > best.path.length) {
|
|
892
|
+
best = { path: [...path41], edges: [...edges] };
|
|
893
893
|
}
|
|
894
|
-
if (
|
|
894
|
+
if (path41.length - 1 >= maxDepth) return;
|
|
895
895
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
896
896
|
for (const [srcId, edge] of incoming) {
|
|
897
897
|
if (visited.has(srcId)) continue;
|
|
898
898
|
visited.add(srcId);
|
|
899
|
-
|
|
899
|
+
path41.push(srcId);
|
|
900
900
|
edges.push(edge);
|
|
901
|
-
step(srcId,
|
|
902
|
-
|
|
901
|
+
step(srcId, path41, edges);
|
|
902
|
+
path41.pop();
|
|
903
903
|
edges.pop();
|
|
904
904
|
visited.delete(srcId);
|
|
905
905
|
}
|
|
@@ -1588,35 +1588,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1588
1588
|
const existing = graph.getEdgeAttributes(id);
|
|
1589
1589
|
const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
|
|
1590
1590
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1591
|
+
const newSignal = {
|
|
1592
|
+
spanCount: newSpanCount,
|
|
1593
|
+
errorCount: newErrorCount,
|
|
1594
|
+
lastObservedAgeMs: 0
|
|
1595
|
+
};
|
|
1591
1596
|
const updated = {
|
|
1592
1597
|
...existing,
|
|
1593
1598
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1594
1599
|
lastObserved: ts,
|
|
1595
1600
|
callCount: newSpanCount,
|
|
1596
|
-
signal:
|
|
1597
|
-
|
|
1598
|
-
errorCount: newErrorCount,
|
|
1599
|
-
lastObservedAgeMs: 0
|
|
1600
|
-
},
|
|
1601
|
-
confidence: 1
|
|
1601
|
+
signal: newSignal,
|
|
1602
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
|
|
1602
1603
|
};
|
|
1603
1604
|
graph.replaceEdgeAttributes(id, updated);
|
|
1604
1605
|
return { edge: updated, created: false };
|
|
1605
1606
|
}
|
|
1607
|
+
const signal = {
|
|
1608
|
+
spanCount: 1,
|
|
1609
|
+
errorCount: isError ? 1 : 0,
|
|
1610
|
+
lastObservedAgeMs: 0
|
|
1611
|
+
};
|
|
1606
1612
|
const edge = {
|
|
1607
1613
|
id,
|
|
1608
1614
|
source,
|
|
1609
1615
|
target,
|
|
1610
1616
|
type,
|
|
1611
1617
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1612
|
-
confidence:
|
|
1618
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1613
1619
|
lastObserved: ts,
|
|
1614
1620
|
callCount: 1,
|
|
1615
|
-
signal
|
|
1616
|
-
spanCount: 1,
|
|
1617
|
-
errorCount: isError ? 1 : 0,
|
|
1618
|
-
lastObservedAgeMs: 0
|
|
1619
|
-
}
|
|
1621
|
+
signal
|
|
1620
1622
|
};
|
|
1621
1623
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1622
1624
|
return { edge, created: true };
|
|
@@ -1936,8 +1938,8 @@ async function readErrorEvents(errorsPath) {
|
|
|
1936
1938
|
|
|
1937
1939
|
// src/extract/services.ts
|
|
1938
1940
|
init_cjs_shims();
|
|
1939
|
-
var
|
|
1940
|
-
var
|
|
1941
|
+
var import_node_fs8 = require("fs");
|
|
1942
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1941
1943
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1942
1944
|
var import_minimatch2 = require("minimatch");
|
|
1943
1945
|
var import_types5 = require("@neat.is/types");
|
|
@@ -1961,9 +1963,107 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1961
1963
|
function isConfigFile(name) {
|
|
1962
1964
|
const ext = import_node_path4.default.extname(name);
|
|
1963
1965
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
1964
|
-
if (name === ".env" || name.startsWith(".env."))
|
|
1966
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
1967
|
+
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
1968
|
+
return { match: true, fileType: "env" };
|
|
1969
|
+
}
|
|
1965
1970
|
return { match: false, fileType: "" };
|
|
1966
1971
|
}
|
|
1972
|
+
function isTestPath(filePath) {
|
|
1973
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
1974
|
+
const segments = normalised.split("/");
|
|
1975
|
+
for (const seg of segments) {
|
|
1976
|
+
if (seg === "__tests__" || seg === "__fixtures__" || seg === "integration-tests") {
|
|
1977
|
+
return true;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
const base = segments[segments.length - 1] ?? "";
|
|
1981
|
+
return /\.(spec|test)\.(?:tsx?|jsx?|mjs|cjs|py)$/i.test(base);
|
|
1982
|
+
}
|
|
1983
|
+
function isEnvTemplateFile(name) {
|
|
1984
|
+
if (name === ".env.template" || name === ".env.example" || name === ".env.sample") {
|
|
1985
|
+
return true;
|
|
1986
|
+
}
|
|
1987
|
+
return /^\.env\.[^.]+\.(?:template|example|sample)$/i.test(name);
|
|
1988
|
+
}
|
|
1989
|
+
function maskCommentsInSource(src) {
|
|
1990
|
+
const len = src.length;
|
|
1991
|
+
const out = new Array(len);
|
|
1992
|
+
let i = 0;
|
|
1993
|
+
let inString = 0;
|
|
1994
|
+
let escaped = false;
|
|
1995
|
+
while (i < len) {
|
|
1996
|
+
const c = src[i];
|
|
1997
|
+
if (inString !== 0) {
|
|
1998
|
+
out[i] = c;
|
|
1999
|
+
if (escaped) {
|
|
2000
|
+
escaped = false;
|
|
2001
|
+
} else if (c === "\\") {
|
|
2002
|
+
escaped = true;
|
|
2003
|
+
} else if (c === inString) {
|
|
2004
|
+
inString = 0;
|
|
2005
|
+
}
|
|
2006
|
+
i++;
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
if (c === "/" && i + 1 < len) {
|
|
2010
|
+
const next = src[i + 1];
|
|
2011
|
+
if (next === "/") {
|
|
2012
|
+
out[i] = " ";
|
|
2013
|
+
out[i + 1] = " ";
|
|
2014
|
+
let j = i + 2;
|
|
2015
|
+
while (j < len && src[j] !== "\n") {
|
|
2016
|
+
out[j] = " ";
|
|
2017
|
+
j++;
|
|
2018
|
+
}
|
|
2019
|
+
i = j;
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
if (next === "*") {
|
|
2023
|
+
out[i] = " ";
|
|
2024
|
+
out[i + 1] = " ";
|
|
2025
|
+
let j = i + 2;
|
|
2026
|
+
while (j < len) {
|
|
2027
|
+
if (src[j] === "\n") {
|
|
2028
|
+
out[j] = "\n";
|
|
2029
|
+
j++;
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
if (src[j] === "*" && j + 1 < len && src[j + 1] === "/") {
|
|
2033
|
+
out[j] = " ";
|
|
2034
|
+
out[j + 1] = " ";
|
|
2035
|
+
j += 2;
|
|
2036
|
+
break;
|
|
2037
|
+
}
|
|
2038
|
+
out[j] = " ";
|
|
2039
|
+
j++;
|
|
2040
|
+
}
|
|
2041
|
+
i = j;
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
out[i] = c;
|
|
2046
|
+
if (c === "'" || c === '"' || c === "`") inString = c;
|
|
2047
|
+
i++;
|
|
2048
|
+
}
|
|
2049
|
+
return out.join("");
|
|
2050
|
+
}
|
|
2051
|
+
var URL_LIKE = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
2052
|
+
function urlMatchesHost(urlString, host) {
|
|
2053
|
+
if (typeof urlString !== "string" || urlString.length === 0) return false;
|
|
2054
|
+
if (!URL_LIKE.test(urlString)) return false;
|
|
2055
|
+
const [wantedHost, wantedPort] = host.split(":");
|
|
2056
|
+
let parsed;
|
|
2057
|
+
try {
|
|
2058
|
+
const candidate = urlString.startsWith("//") ? `http:${urlString}` : urlString;
|
|
2059
|
+
parsed = new URL(candidate);
|
|
2060
|
+
} catch {
|
|
2061
|
+
return false;
|
|
2062
|
+
}
|
|
2063
|
+
if (parsed.hostname.toLowerCase() !== (wantedHost ?? "").toLowerCase()) return false;
|
|
2064
|
+
if (wantedPort && parsed.port !== wantedPort) return false;
|
|
2065
|
+
return true;
|
|
2066
|
+
}
|
|
1967
2067
|
function cleanVersion(raw) {
|
|
1968
2068
|
if (!raw) return void 0;
|
|
1969
2069
|
return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
|
|
@@ -2119,6 +2219,62 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
|
2119
2219
|
return author ?? void 0;
|
|
2120
2220
|
}
|
|
2121
2221
|
|
|
2222
|
+
// src/extract/errors.ts
|
|
2223
|
+
init_cjs_shims();
|
|
2224
|
+
var import_node_fs7 = require("fs");
|
|
2225
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2226
|
+
var sink = [];
|
|
2227
|
+
function recordExtractionError(producer, file, err) {
|
|
2228
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
2229
|
+
sink.push({
|
|
2230
|
+
producer,
|
|
2231
|
+
file,
|
|
2232
|
+
error: e.message,
|
|
2233
|
+
stack: e.stack,
|
|
2234
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2235
|
+
source: "extract"
|
|
2236
|
+
});
|
|
2237
|
+
console.warn(`[neat] ${producer} skipped ${file}: ${e.message}`);
|
|
2238
|
+
}
|
|
2239
|
+
function drainExtractionErrors() {
|
|
2240
|
+
return sink.splice(0, sink.length);
|
|
2241
|
+
}
|
|
2242
|
+
async function writeExtractionErrors(errors, errorsPath) {
|
|
2243
|
+
if (errors.length === 0) return;
|
|
2244
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(errorsPath), { recursive: true });
|
|
2245
|
+
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2246
|
+
await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
|
|
2247
|
+
}
|
|
2248
|
+
function isStrictExtractionEnabled() {
|
|
2249
|
+
const raw = process.env.NEAT_STRICT_EXTRACTION;
|
|
2250
|
+
return raw === "1" || raw === "true";
|
|
2251
|
+
}
|
|
2252
|
+
function formatExtractionBanner(count) {
|
|
2253
|
+
if (count === 1) return `[neat] 1 file skipped due to parse errors`;
|
|
2254
|
+
return `[neat] ${count} files skipped due to parse errors`;
|
|
2255
|
+
}
|
|
2256
|
+
var droppedSink = [];
|
|
2257
|
+
function noteExtractedDropped(edge) {
|
|
2258
|
+
droppedSink.push(edge);
|
|
2259
|
+
}
|
|
2260
|
+
function drainDroppedExtracted() {
|
|
2261
|
+
return droppedSink.splice(0, droppedSink.length);
|
|
2262
|
+
}
|
|
2263
|
+
function isRejectedLogEnabled() {
|
|
2264
|
+
const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
|
|
2265
|
+
return raw === "1" || raw === "true";
|
|
2266
|
+
}
|
|
2267
|
+
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2268
|
+
if (drops.length === 0) return;
|
|
2269
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
|
|
2270
|
+
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2271
|
+
await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2272
|
+
}
|
|
2273
|
+
function formatPrecisionFloorBanner(count) {
|
|
2274
|
+
if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
|
|
2275
|
+
return `[neat] ${count} extracted edges dropped below precision floor`;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2122
2278
|
// src/extract/services.ts
|
|
2123
2279
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
2124
2280
|
function parseScanDepth() {
|
|
@@ -2135,21 +2291,21 @@ function workspaceGlobs(pkg) {
|
|
|
2135
2291
|
return null;
|
|
2136
2292
|
}
|
|
2137
2293
|
async function loadGitignore(scanPath) {
|
|
2138
|
-
const gitignorePath =
|
|
2294
|
+
const gitignorePath = import_node_path8.default.join(scanPath, ".gitignore");
|
|
2139
2295
|
if (!await exists(gitignorePath)) return null;
|
|
2140
|
-
const raw = await
|
|
2296
|
+
const raw = await import_node_fs8.promises.readFile(gitignorePath, "utf8");
|
|
2141
2297
|
return (0, import_ignore.default)().add(raw);
|
|
2142
2298
|
}
|
|
2143
2299
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2144
2300
|
async function recurse(current, depth) {
|
|
2145
2301
|
if (depth > options.maxDepth) return;
|
|
2146
|
-
const entries = await
|
|
2302
|
+
const entries = await import_node_fs8.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2147
2303
|
for (const entry2 of entries) {
|
|
2148
2304
|
if (!entry2.isDirectory()) continue;
|
|
2149
2305
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2150
|
-
const child =
|
|
2306
|
+
const child = import_node_path8.default.join(current, entry2.name);
|
|
2151
2307
|
if (options.ig) {
|
|
2152
|
-
const rel =
|
|
2308
|
+
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2153
2309
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2154
2310
|
}
|
|
2155
2311
|
await visit(child);
|
|
@@ -2164,8 +2320,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2164
2320
|
for (const raw of globs) {
|
|
2165
2321
|
const pattern = raw.replace(/^\.\//, "");
|
|
2166
2322
|
if (!pattern.includes("*")) {
|
|
2167
|
-
const candidate =
|
|
2168
|
-
if (await exists(
|
|
2323
|
+
const candidate = import_node_path8.default.join(scanPath, pattern);
|
|
2324
|
+
if (await exists(import_node_path8.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2169
2325
|
continue;
|
|
2170
2326
|
}
|
|
2171
2327
|
const segments = pattern.split("/");
|
|
@@ -2174,13 +2330,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2174
2330
|
if (seg.includes("*")) break;
|
|
2175
2331
|
staticSegments.push(seg);
|
|
2176
2332
|
}
|
|
2177
|
-
const start =
|
|
2333
|
+
const start = import_node_path8.default.join(scanPath, ...staticSegments);
|
|
2178
2334
|
if (!await exists(start)) continue;
|
|
2179
2335
|
const hasDoubleStar = pattern.includes("**");
|
|
2180
2336
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2181
2337
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2182
|
-
const rel =
|
|
2183
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
2338
|
+
const rel = import_node_path8.default.relative(scanPath, dir).split(import_node_path8.default.sep).join("/");
|
|
2339
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2184
2340
|
found.add(dir);
|
|
2185
2341
|
}
|
|
2186
2342
|
});
|
|
@@ -2188,15 +2344,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2188
2344
|
return [...found];
|
|
2189
2345
|
}
|
|
2190
2346
|
async function discoverNodeService(scanPath, dir) {
|
|
2191
|
-
const pkgPath =
|
|
2347
|
+
const pkgPath = import_node_path8.default.join(dir, "package.json");
|
|
2192
2348
|
if (!await exists(pkgPath)) return null;
|
|
2193
2349
|
let pkg;
|
|
2194
2350
|
try {
|
|
2195
2351
|
pkg = await readJson(pkgPath);
|
|
2196
2352
|
} catch (err) {
|
|
2197
|
-
|
|
2198
|
-
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2199
|
-
);
|
|
2353
|
+
recordExtractionError("services", import_node_path8.default.relative(scanPath, pkgPath), err);
|
|
2200
2354
|
return null;
|
|
2201
2355
|
}
|
|
2202
2356
|
if (!pkg.name) return null;
|
|
@@ -2207,7 +2361,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2207
2361
|
language: "javascript",
|
|
2208
2362
|
version: pkg.version,
|
|
2209
2363
|
dependencies: pkg.dependencies ?? {},
|
|
2210
|
-
repoPath:
|
|
2364
|
+
repoPath: import_node_path8.default.relative(scanPath, dir),
|
|
2211
2365
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2212
2366
|
};
|
|
2213
2367
|
return { pkg, dir, node };
|
|
@@ -2223,19 +2377,21 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2223
2377
|
language: "python",
|
|
2224
2378
|
version: py.version,
|
|
2225
2379
|
dependencies: py.dependencies,
|
|
2226
|
-
repoPath:
|
|
2380
|
+
repoPath: import_node_path8.default.relative(scanPath, dir)
|
|
2227
2381
|
};
|
|
2228
2382
|
return { pkg, dir, node };
|
|
2229
2383
|
}
|
|
2230
2384
|
async function discoverServices(scanPath) {
|
|
2231
|
-
const rootPkgPath =
|
|
2385
|
+
const rootPkgPath = import_node_path8.default.join(scanPath, "package.json");
|
|
2232
2386
|
let rootPkg = null;
|
|
2233
2387
|
if (await exists(rootPkgPath)) {
|
|
2234
2388
|
try {
|
|
2235
2389
|
rootPkg = await readJson(rootPkgPath);
|
|
2236
2390
|
} catch (err) {
|
|
2237
|
-
|
|
2238
|
-
|
|
2391
|
+
recordExtractionError(
|
|
2392
|
+
"services workspaces",
|
|
2393
|
+
import_node_path8.default.relative(scanPath, rootPkgPath),
|
|
2394
|
+
err
|
|
2239
2395
|
);
|
|
2240
2396
|
}
|
|
2241
2397
|
}
|
|
@@ -2251,9 +2407,9 @@ async function discoverServices(scanPath) {
|
|
|
2251
2407
|
scanPath,
|
|
2252
2408
|
{ maxDepth: parseScanDepth(), ig },
|
|
2253
2409
|
async (dir) => {
|
|
2254
|
-
if (await exists(
|
|
2410
|
+
if (await exists(import_node_path8.default.join(dir, "package.json"))) {
|
|
2255
2411
|
candidateDirs.push(dir);
|
|
2256
|
-
} else if (await exists(
|
|
2412
|
+
} 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"))) {
|
|
2257
2413
|
candidateDirs.push(dir);
|
|
2258
2414
|
}
|
|
2259
2415
|
}
|
|
@@ -2267,8 +2423,8 @@ async function discoverServices(scanPath) {
|
|
|
2267
2423
|
if (!service) continue;
|
|
2268
2424
|
const existingDir = seen.get(service.node.name);
|
|
2269
2425
|
if (existingDir !== void 0) {
|
|
2270
|
-
const a =
|
|
2271
|
-
const b =
|
|
2426
|
+
const a = import_node_path8.default.relative(scanPath, existingDir) || ".";
|
|
2427
|
+
const b = import_node_path8.default.relative(scanPath, dir) || ".";
|
|
2272
2428
|
console.warn(
|
|
2273
2429
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2274
2430
|
);
|
|
@@ -2305,8 +2461,8 @@ function addServiceNodes(graph, services) {
|
|
|
2305
2461
|
|
|
2306
2462
|
// src/extract/aliases.ts
|
|
2307
2463
|
init_cjs_shims();
|
|
2308
|
-
var
|
|
2309
|
-
var
|
|
2464
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2465
|
+
var import_node_fs9 = require("fs");
|
|
2310
2466
|
var import_yaml2 = require("yaml");
|
|
2311
2467
|
var import_types6 = require("@neat.is/types");
|
|
2312
2468
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2333,14 +2489,14 @@ function indexServicesByName(services) {
|
|
|
2333
2489
|
const map = /* @__PURE__ */ new Map();
|
|
2334
2490
|
for (const s of services) {
|
|
2335
2491
|
map.set(s.node.name, s.node.id);
|
|
2336
|
-
map.set(
|
|
2492
|
+
map.set(import_node_path9.default.basename(s.dir), s.node.id);
|
|
2337
2493
|
}
|
|
2338
2494
|
return map;
|
|
2339
2495
|
}
|
|
2340
2496
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2341
2497
|
let composePath = null;
|
|
2342
2498
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2343
|
-
const abs =
|
|
2499
|
+
const abs = import_node_path9.default.join(scanPath, name);
|
|
2344
2500
|
if (await exists(abs)) {
|
|
2345
2501
|
composePath = abs;
|
|
2346
2502
|
break;
|
|
@@ -2351,8 +2507,10 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
2351
2507
|
try {
|
|
2352
2508
|
compose = await readYaml(composePath);
|
|
2353
2509
|
} catch (err) {
|
|
2354
|
-
|
|
2355
|
-
|
|
2510
|
+
recordExtractionError(
|
|
2511
|
+
"aliases compose",
|
|
2512
|
+
import_node_path9.default.relative(scanPath, composePath),
|
|
2513
|
+
err
|
|
2356
2514
|
);
|
|
2357
2515
|
return;
|
|
2358
2516
|
}
|
|
@@ -2394,15 +2552,13 @@ function parseDockerfileLabels(content) {
|
|
|
2394
2552
|
}
|
|
2395
2553
|
async function collectDockerfileAliases(graph, services) {
|
|
2396
2554
|
for (const service of services) {
|
|
2397
|
-
const dockerfilePath =
|
|
2555
|
+
const dockerfilePath = import_node_path9.default.join(service.dir, "Dockerfile");
|
|
2398
2556
|
if (!await exists(dockerfilePath)) continue;
|
|
2399
2557
|
let content;
|
|
2400
2558
|
try {
|
|
2401
|
-
content = await
|
|
2559
|
+
content = await import_node_fs9.promises.readFile(dockerfilePath, "utf8");
|
|
2402
2560
|
} catch (err) {
|
|
2403
|
-
|
|
2404
|
-
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2405
|
-
);
|
|
2561
|
+
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
2406
2562
|
continue;
|
|
2407
2563
|
}
|
|
2408
2564
|
const aliases = parseDockerfileLabels(content);
|
|
@@ -2412,13 +2568,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2412
2568
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2413
2569
|
if (depth > max) return [];
|
|
2414
2570
|
const out = [];
|
|
2415
|
-
const entries = await
|
|
2571
|
+
const entries = await import_node_fs9.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2416
2572
|
for (const entry2 of entries) {
|
|
2417
2573
|
if (entry2.isDirectory()) {
|
|
2418
2574
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2419
|
-
out.push(...await walkYamlFiles(
|
|
2420
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2421
|
-
out.push(
|
|
2575
|
+
out.push(...await walkYamlFiles(import_node_path9.default.join(start, entry2.name), depth + 1, max));
|
|
2576
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry2.name))) {
|
|
2577
|
+
out.push(import_node_path9.default.join(start, entry2.name));
|
|
2422
2578
|
}
|
|
2423
2579
|
}
|
|
2424
2580
|
return out;
|
|
@@ -2445,7 +2601,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2445
2601
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2446
2602
|
const files = await walkYamlFiles(scanPath);
|
|
2447
2603
|
for (const file of files) {
|
|
2448
|
-
const content = await
|
|
2604
|
+
const content = await import_node_fs9.promises.readFile(file, "utf8");
|
|
2449
2605
|
let docs;
|
|
2450
2606
|
try {
|
|
2451
2607
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2470,14 +2626,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2470
2626
|
|
|
2471
2627
|
// src/extract/databases/index.ts
|
|
2472
2628
|
init_cjs_shims();
|
|
2473
|
-
var
|
|
2629
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2474
2630
|
var import_types7 = require("@neat.is/types");
|
|
2475
2631
|
|
|
2476
2632
|
// src/extract/databases/db-config-yaml.ts
|
|
2477
2633
|
init_cjs_shims();
|
|
2478
|
-
var
|
|
2634
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2479
2635
|
async function parse(serviceDir) {
|
|
2480
|
-
const yamlPath =
|
|
2636
|
+
const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
|
|
2481
2637
|
if (!await exists(yamlPath)) return [];
|
|
2482
2638
|
const raw = await readYaml(yamlPath);
|
|
2483
2639
|
return [
|
|
@@ -2495,13 +2651,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2495
2651
|
|
|
2496
2652
|
// src/extract/databases/dotenv.ts
|
|
2497
2653
|
init_cjs_shims();
|
|
2498
|
-
var
|
|
2499
|
-
var
|
|
2654
|
+
var import_node_fs11 = require("fs");
|
|
2655
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2500
2656
|
|
|
2501
2657
|
// src/extract/databases/shared.ts
|
|
2502
2658
|
init_cjs_shims();
|
|
2503
|
-
var
|
|
2504
|
-
var
|
|
2659
|
+
var import_node_fs10 = require("fs");
|
|
2660
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2505
2661
|
function schemeToEngine(scheme) {
|
|
2506
2662
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2507
2663
|
switch (s) {
|
|
@@ -2540,14 +2696,14 @@ function parseConnectionString(url) {
|
|
|
2540
2696
|
}
|
|
2541
2697
|
async function readIfExists(filePath) {
|
|
2542
2698
|
try {
|
|
2543
|
-
return await
|
|
2699
|
+
return await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2544
2700
|
} catch {
|
|
2545
2701
|
return null;
|
|
2546
2702
|
}
|
|
2547
2703
|
}
|
|
2548
2704
|
async function findFirst(serviceDir, candidates) {
|
|
2549
2705
|
for (const rel of candidates) {
|
|
2550
|
-
const abs =
|
|
2706
|
+
const abs = import_node_path11.default.join(serviceDir, rel);
|
|
2551
2707
|
const content = await readIfExists(abs);
|
|
2552
2708
|
if (content !== null) return abs;
|
|
2553
2709
|
}
|
|
@@ -2598,15 +2754,15 @@ function parseDotenvLine(line) {
|
|
|
2598
2754
|
return { key, value };
|
|
2599
2755
|
}
|
|
2600
2756
|
async function parse2(serviceDir) {
|
|
2601
|
-
const entries = await
|
|
2757
|
+
const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2602
2758
|
const configs = [];
|
|
2603
2759
|
const seen = /* @__PURE__ */ new Set();
|
|
2604
2760
|
for (const entry2 of entries) {
|
|
2605
2761
|
if (!entry2.isFile()) continue;
|
|
2606
2762
|
const match = isConfigFile(entry2.name);
|
|
2607
2763
|
if (!match.match || match.fileType !== "env") continue;
|
|
2608
|
-
const filePath =
|
|
2609
|
-
const content = await
|
|
2764
|
+
const filePath = import_node_path12.default.join(serviceDir, entry2.name);
|
|
2765
|
+
const content = await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
2610
2766
|
for (const line of content.split("\n")) {
|
|
2611
2767
|
const parsed = parseDotenvLine(line);
|
|
2612
2768
|
if (!parsed) continue;
|
|
@@ -2625,9 +2781,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2625
2781
|
|
|
2626
2782
|
// src/extract/databases/prisma.ts
|
|
2627
2783
|
init_cjs_shims();
|
|
2628
|
-
var
|
|
2784
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2629
2785
|
async function parse3(serviceDir) {
|
|
2630
|
-
const schemaPath =
|
|
2786
|
+
const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2631
2787
|
const content = await readIfExists(schemaPath);
|
|
2632
2788
|
if (!content) return [];
|
|
2633
2789
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2759,10 +2915,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2759
2915
|
|
|
2760
2916
|
// src/extract/databases/ormconfig.ts
|
|
2761
2917
|
init_cjs_shims();
|
|
2762
|
-
var
|
|
2918
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2763
2919
|
async function parse6(serviceDir) {
|
|
2764
2920
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2765
|
-
const abs =
|
|
2921
|
+
const abs = import_node_path14.default.join(serviceDir, candidate);
|
|
2766
2922
|
if (!await exists(abs)) continue;
|
|
2767
2923
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2768
2924
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2822,9 +2978,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2822
2978
|
|
|
2823
2979
|
// src/extract/databases/sequelize.ts
|
|
2824
2980
|
init_cjs_shims();
|
|
2825
|
-
var
|
|
2981
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2826
2982
|
async function parse8(serviceDir) {
|
|
2827
|
-
const configPath =
|
|
2983
|
+
const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
|
|
2828
2984
|
if (!await exists(configPath)) return [];
|
|
2829
2985
|
const raw = await readJson(configPath);
|
|
2830
2986
|
const out = [];
|
|
@@ -2851,7 +3007,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2851
3007
|
|
|
2852
3008
|
// src/extract/databases/docker-compose.ts
|
|
2853
3009
|
init_cjs_shims();
|
|
2854
|
-
var
|
|
3010
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2855
3011
|
function portFromService(svc) {
|
|
2856
3012
|
for (const raw of svc.ports ?? []) {
|
|
2857
3013
|
const str = String(raw);
|
|
@@ -2878,7 +3034,7 @@ function databaseFromEnv(svc) {
|
|
|
2878
3034
|
}
|
|
2879
3035
|
async function parse9(serviceDir) {
|
|
2880
3036
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2881
|
-
const abs =
|
|
3037
|
+
const abs = import_node_path16.default.join(serviceDir, name);
|
|
2882
3038
|
if (!await exists(abs)) continue;
|
|
2883
3039
|
const raw = await readYaml(abs);
|
|
2884
3040
|
if (!raw?.services) return [];
|
|
@@ -3056,11 +3212,13 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3056
3212
|
target: dbNode.id,
|
|
3057
3213
|
type: import_types7.EdgeType.CONNECTS_TO,
|
|
3058
3214
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3215
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3216
|
+
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
3217
|
+
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3218
|
+
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3219
|
+
evidence: {
|
|
3220
|
+
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3221
|
+
}
|
|
3064
3222
|
};
|
|
3065
3223
|
if (!graph.hasEdge(edge.id)) {
|
|
3066
3224
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3086,15 +3244,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3086
3244
|
|
|
3087
3245
|
// src/extract/configs.ts
|
|
3088
3246
|
init_cjs_shims();
|
|
3089
|
-
var
|
|
3090
|
-
var
|
|
3247
|
+
var import_node_fs12 = require("fs");
|
|
3248
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3091
3249
|
var import_types8 = require("@neat.is/types");
|
|
3092
3250
|
async function walkConfigFiles(dir) {
|
|
3093
3251
|
const out = [];
|
|
3094
3252
|
async function walk(current) {
|
|
3095
|
-
const entries = await
|
|
3253
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
|
|
3096
3254
|
for (const entry2 of entries) {
|
|
3097
|
-
const full =
|
|
3255
|
+
const full = import_node_path18.default.join(current, entry2.name);
|
|
3098
3256
|
if (entry2.isDirectory()) {
|
|
3099
3257
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
3100
3258
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
@@ -3111,13 +3269,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3111
3269
|
for (const service of services) {
|
|
3112
3270
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3113
3271
|
for (const file of configFiles) {
|
|
3114
|
-
const relPath =
|
|
3272
|
+
const relPath = import_node_path18.default.relative(scanPath, file);
|
|
3115
3273
|
const node = {
|
|
3116
3274
|
id: (0, import_types8.configId)(relPath),
|
|
3117
3275
|
type: import_types8.NodeType.ConfigNode,
|
|
3118
|
-
name:
|
|
3276
|
+
name: import_node_path18.default.basename(file),
|
|
3119
3277
|
path: relPath,
|
|
3120
|
-
fileType: isConfigFile(
|
|
3278
|
+
fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
|
|
3121
3279
|
};
|
|
3122
3280
|
if (!graph.hasNode(node.id)) {
|
|
3123
3281
|
graph.addNode(node.id, node);
|
|
@@ -3129,7 +3287,8 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3129
3287
|
target: node.id,
|
|
3130
3288
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
3131
3289
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
3132
|
-
|
|
3290
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3291
|
+
evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
|
|
3133
3292
|
};
|
|
3134
3293
|
if (!graph.hasEdge(edge.id)) {
|
|
3135
3294
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3146,7 +3305,7 @@ var import_types14 = require("@neat.is/types");
|
|
|
3146
3305
|
|
|
3147
3306
|
// src/extract/calls/http.ts
|
|
3148
3307
|
init_cjs_shims();
|
|
3149
|
-
var
|
|
3308
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3150
3309
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3151
3310
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3152
3311
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -3154,17 +3313,17 @@ var import_types9 = require("@neat.is/types");
|
|
|
3154
3313
|
|
|
3155
3314
|
// src/extract/calls/shared.ts
|
|
3156
3315
|
init_cjs_shims();
|
|
3157
|
-
var
|
|
3158
|
-
var
|
|
3316
|
+
var import_node_fs13 = require("fs");
|
|
3317
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3159
3318
|
async function walkSourceFiles(dir) {
|
|
3160
3319
|
const out = [];
|
|
3161
3320
|
async function walk(current) {
|
|
3162
|
-
const entries = await
|
|
3321
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3163
3322
|
for (const entry2 of entries) {
|
|
3164
|
-
const full =
|
|
3323
|
+
const full = import_node_path19.default.join(current, entry2.name);
|
|
3165
3324
|
if (entry2.isDirectory()) {
|
|
3166
3325
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
3167
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3326
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3168
3327
|
out.push(full);
|
|
3169
3328
|
}
|
|
3170
3329
|
}
|
|
@@ -3177,7 +3336,7 @@ async function loadSourceFiles(dir) {
|
|
|
3177
3336
|
const out = [];
|
|
3178
3337
|
for (const p of paths) {
|
|
3179
3338
|
try {
|
|
3180
|
-
const content = await
|
|
3339
|
+
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3181
3340
|
out.push({ path: p, content });
|
|
3182
3341
|
} catch {
|
|
3183
3342
|
}
|
|
@@ -3196,8 +3355,27 @@ function snippet(text, line) {
|
|
|
3196
3355
|
|
|
3197
3356
|
// src/extract/calls/http.ts
|
|
3198
3357
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3358
|
+
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3359
|
+
function isInsideJsxExternalLink(node) {
|
|
3360
|
+
let cursor = node.parent;
|
|
3361
|
+
while (cursor) {
|
|
3362
|
+
if (cursor.type === "jsx_attribute") {
|
|
3363
|
+
let owner = cursor.parent;
|
|
3364
|
+
while (owner && owner.type !== "jsx_opening_element" && owner.type !== "jsx_self_closing_element") {
|
|
3365
|
+
owner = owner.parent;
|
|
3366
|
+
}
|
|
3367
|
+
if (!owner) return false;
|
|
3368
|
+
const tagNode = owner.namedChild(0);
|
|
3369
|
+
const tagName = tagNode?.text ?? "";
|
|
3370
|
+
const right = tagName.includes(".") ? tagName.split(".").pop() : tagName;
|
|
3371
|
+
return JSX_EXTERNAL_LINK_TAGS.has(right);
|
|
3372
|
+
}
|
|
3373
|
+
cursor = cursor.parent;
|
|
3374
|
+
}
|
|
3375
|
+
return false;
|
|
3376
|
+
}
|
|
3199
3377
|
function collectStringLiterals(node, out) {
|
|
3200
|
-
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push(node.text);
|
|
3378
|
+
if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node });
|
|
3201
3379
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3202
3380
|
const child = node.namedChild(i);
|
|
3203
3381
|
if (child) collectStringLiterals(child, out);
|
|
@@ -3209,8 +3387,9 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3209
3387
|
collectStringLiterals(tree.rootNode, literals);
|
|
3210
3388
|
const targets = /* @__PURE__ */ new Set();
|
|
3211
3389
|
for (const lit of literals) {
|
|
3390
|
+
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3212
3391
|
for (const host of knownHosts) {
|
|
3213
|
-
if (lit.
|
|
3392
|
+
if (urlMatchesHost(lit.text, host)) {
|
|
3214
3393
|
targets.add(host);
|
|
3215
3394
|
}
|
|
3216
3395
|
}
|
|
@@ -3233,9 +3412,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3233
3412
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3234
3413
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3235
3414
|
for (const service of services) {
|
|
3236
|
-
knownHosts.add(
|
|
3415
|
+
knownHosts.add(import_node_path20.default.basename(service.dir));
|
|
3237
3416
|
knownHosts.add(service.pkg.name);
|
|
3238
|
-
hostToNodeId.set(
|
|
3417
|
+
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3239
3418
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3240
3419
|
}
|
|
3241
3420
|
let edgesAdded = 0;
|
|
@@ -3243,14 +3422,13 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3243
3422
|
const files = await loadSourceFiles(service.dir);
|
|
3244
3423
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3245
3424
|
for (const file of files) {
|
|
3246
|
-
|
|
3425
|
+
if (isTestPath(file.path)) continue;
|
|
3426
|
+
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3247
3427
|
let targets;
|
|
3248
3428
|
try {
|
|
3249
3429
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
3250
3430
|
} catch (err) {
|
|
3251
|
-
|
|
3252
|
-
`[neat] http call extraction skipped ${file.path}: ${err.message}`
|
|
3253
|
-
);
|
|
3431
|
+
recordExtractionError("http call extraction", file.path, err);
|
|
3254
3432
|
continue;
|
|
3255
3433
|
}
|
|
3256
3434
|
for (const t of targets) {
|
|
@@ -3264,17 +3442,32 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3264
3442
|
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3265
3443
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3266
3444
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3445
|
+
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3446
|
+
const ev = {
|
|
3447
|
+
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3448
|
+
line,
|
|
3449
|
+
snippet: snippet(fileContent, line)
|
|
3450
|
+
};
|
|
3451
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3452
|
+
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3453
|
+
noteExtractedDropped({
|
|
3454
|
+
source: service.node.id,
|
|
3455
|
+
target: targetId,
|
|
3456
|
+
type: import_types9.EdgeType.CALLS,
|
|
3457
|
+
confidence,
|
|
3458
|
+
confidenceKind: "hostname-shape-match",
|
|
3459
|
+
evidence: ev
|
|
3460
|
+
});
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3267
3463
|
const edge = {
|
|
3268
|
-
id:
|
|
3464
|
+
id: edgeId,
|
|
3269
3465
|
source: service.node.id,
|
|
3270
3466
|
target: targetId,
|
|
3271
3467
|
type: import_types9.EdgeType.CALLS,
|
|
3272
3468
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
line,
|
|
3276
|
-
snippet: snippet(fileContent, line)
|
|
3277
|
-
}
|
|
3469
|
+
confidence,
|
|
3470
|
+
evidence: ev
|
|
3278
3471
|
};
|
|
3279
3472
|
if (!graph.hasEdge(edge.id)) {
|
|
3280
3473
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3287,7 +3480,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3287
3480
|
|
|
3288
3481
|
// src/extract/calls/kafka.ts
|
|
3289
3482
|
init_cjs_shims();
|
|
3290
|
-
var
|
|
3483
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3291
3484
|
var import_types10 = require("@neat.is/types");
|
|
3292
3485
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3293
3486
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3313,8 +3506,12 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3313
3506
|
name: topic,
|
|
3314
3507
|
kind: "kafka-topic",
|
|
3315
3508
|
edgeType,
|
|
3509
|
+
// `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
|
|
3510
|
+
// framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
|
|
3511
|
+
// tier (ADR-066).
|
|
3512
|
+
confidenceKind: "verified-call-site",
|
|
3316
3513
|
evidence: {
|
|
3317
|
-
file:
|
|
3514
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3318
3515
|
line,
|
|
3319
3516
|
snippet: snippet(file.content, line)
|
|
3320
3517
|
}
|
|
@@ -3327,7 +3524,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3327
3524
|
|
|
3328
3525
|
// src/extract/calls/redis.ts
|
|
3329
3526
|
init_cjs_shims();
|
|
3330
|
-
var
|
|
3527
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3331
3528
|
var import_types11 = require("@neat.is/types");
|
|
3332
3529
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3333
3530
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3345,8 +3542,12 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3345
3542
|
name: host,
|
|
3346
3543
|
kind: "redis",
|
|
3347
3544
|
edgeType: "CALLS",
|
|
3545
|
+
// `redis://host` URL literal — the scheme is structural support, but no
|
|
3546
|
+
// call expression is verified to wire it through. URL-with-structural-
|
|
3547
|
+
// support tier (ADR-066).
|
|
3548
|
+
confidenceKind: "url-with-structural-support",
|
|
3348
3549
|
evidence: {
|
|
3349
|
-
file:
|
|
3550
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3350
3551
|
line,
|
|
3351
3552
|
snippet: snippet(file.content, line)
|
|
3352
3553
|
}
|
|
@@ -3357,7 +3558,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3357
3558
|
|
|
3358
3559
|
// src/extract/calls/aws.ts
|
|
3359
3560
|
init_cjs_shims();
|
|
3360
|
-
var
|
|
3561
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3361
3562
|
var import_types12 = require("@neat.is/types");
|
|
3362
3563
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3363
3564
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3386,8 +3587,12 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3386
3587
|
name,
|
|
3387
3588
|
kind,
|
|
3388
3589
|
edgeType: "CALLS",
|
|
3590
|
+
// SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
|
|
3591
|
+
// literal — framework-aware recognizer, verified-call-site tier
|
|
3592
|
+
// (ADR-066).
|
|
3593
|
+
confidenceKind: "verified-call-site",
|
|
3389
3594
|
evidence: {
|
|
3390
|
-
file:
|
|
3595
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3391
3596
|
line,
|
|
3392
3597
|
snippet: snippet(file.content, line)
|
|
3393
3598
|
}
|
|
@@ -3412,16 +3617,42 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3412
3617
|
|
|
3413
3618
|
// src/extract/calls/grpc.ts
|
|
3414
3619
|
init_cjs_shims();
|
|
3415
|
-
var
|
|
3620
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3416
3621
|
var import_types13 = require("@neat.is/types");
|
|
3417
3622
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3623
|
+
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3624
|
+
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
3418
3625
|
function isLikelyAddress(value) {
|
|
3419
3626
|
if (!value) return false;
|
|
3420
3627
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
3421
3628
|
}
|
|
3629
|
+
function normaliseForMatch(s) {
|
|
3630
|
+
return s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3631
|
+
}
|
|
3632
|
+
function readImports(content) {
|
|
3633
|
+
const awsSdkSuffixes = /* @__PURE__ */ new Map();
|
|
3634
|
+
AWS_SDK_IMPORT_RE.lastIndex = 0;
|
|
3635
|
+
let m;
|
|
3636
|
+
while ((m = AWS_SDK_IMPORT_RE.exec(content)) !== null) {
|
|
3637
|
+
const raw = m[1];
|
|
3638
|
+
awsSdkSuffixes.set(normaliseForMatch(raw), raw);
|
|
3639
|
+
}
|
|
3640
|
+
return {
|
|
3641
|
+
awsSdkSuffixes,
|
|
3642
|
+
hasGrpcImport: GRPC_IMPORT_RE.test(content)
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
function classifyClient(symbol, ctx) {
|
|
3646
|
+
const key = normaliseForMatch(symbol);
|
|
3647
|
+
const awsRaw = ctx.awsSdkSuffixes.get(key);
|
|
3648
|
+
if (awsRaw) return { kind: `aws-${awsRaw}` };
|
|
3649
|
+
if (ctx.hasGrpcImport) return { kind: "grpc-service" };
|
|
3650
|
+
return null;
|
|
3651
|
+
}
|
|
3422
3652
|
function grpcEndpointsFromFile(file, serviceDir) {
|
|
3423
3653
|
const out = [];
|
|
3424
3654
|
const seen = /* @__PURE__ */ new Set();
|
|
3655
|
+
const ctx = readImports(file.content);
|
|
3425
3656
|
GRPC_CLIENT_RE.lastIndex = 0;
|
|
3426
3657
|
let m;
|
|
3427
3658
|
while ((m = GRPC_CLIENT_RE.exec(file.content)) !== null) {
|
|
@@ -3429,15 +3660,22 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3429
3660
|
const addr = m[2]?.trim();
|
|
3430
3661
|
const name = isLikelyAddress(addr) ? addr : symbol;
|
|
3431
3662
|
if (seen.has(name)) continue;
|
|
3663
|
+
const classified = classifyClient(symbol, ctx);
|
|
3664
|
+
if (!classified) continue;
|
|
3432
3665
|
seen.add(name);
|
|
3666
|
+
const { kind } = classified;
|
|
3433
3667
|
const line = lineOf(file.content, m[0]);
|
|
3434
3668
|
out.push({
|
|
3435
|
-
infraId: (0, import_types13.infraId)(
|
|
3669
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3436
3670
|
name,
|
|
3437
|
-
kind
|
|
3671
|
+
kind,
|
|
3438
3672
|
edgeType: "CALLS",
|
|
3673
|
+
// `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
|
|
3674
|
+
// context — import-aware classification per #238. Verified-call-site
|
|
3675
|
+
// tier (ADR-066).
|
|
3676
|
+
confidenceKind: "verified-call-site",
|
|
3439
3677
|
evidence: {
|
|
3440
|
-
file:
|
|
3678
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3441
3679
|
line,
|
|
3442
3680
|
snippet: snippet(file.content, line)
|
|
3443
3681
|
}
|
|
@@ -3457,6 +3695,9 @@ function edgeTypeFromEndpoint(ep) {
|
|
|
3457
3695
|
return import_types14.EdgeType.CALLS;
|
|
3458
3696
|
}
|
|
3459
3697
|
}
|
|
3698
|
+
function isAwsKind(kind) {
|
|
3699
|
+
return kind.startsWith("aws-") || kind.startsWith("s3") || kind.startsWith("dynamodb");
|
|
3700
|
+
}
|
|
3460
3701
|
async function addExternalEndpointEdges(graph, services) {
|
|
3461
3702
|
let nodesAdded = 0;
|
|
3462
3703
|
let edgesAdded = 0;
|
|
@@ -3464,10 +3705,13 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3464
3705
|
const files = await loadSourceFiles(service.dir);
|
|
3465
3706
|
const endpoints = [];
|
|
3466
3707
|
for (const file of files) {
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
endpoints.push(...
|
|
3708
|
+
if (isTestPath(file.path)) continue;
|
|
3709
|
+
const masked = maskCommentsInSource(file.content);
|
|
3710
|
+
const maskedFile = { path: file.path, content: masked };
|
|
3711
|
+
endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
|
|
3712
|
+
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
3713
|
+
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
3714
|
+
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
3471
3715
|
}
|
|
3472
3716
|
if (endpoints.length === 0) continue;
|
|
3473
3717
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
@@ -3477,7 +3721,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3477
3721
|
id: ep.infraId,
|
|
3478
3722
|
type: import_types14.NodeType.InfraNode,
|
|
3479
3723
|
name: ep.name,
|
|
3480
|
-
|
|
3724
|
+
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3725
|
+
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
3726
|
+
// bucket / table kinds from aws.ts.
|
|
3727
|
+
provider: isAwsKind(ep.kind) ? "aws" : "self",
|
|
3481
3728
|
kind: ep.kind
|
|
3482
3729
|
};
|
|
3483
3730
|
graph.addNode(node.id, node);
|
|
@@ -3487,6 +3734,18 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3487
3734
|
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
|
|
3488
3735
|
if (seenEdges.has(edgeId)) continue;
|
|
3489
3736
|
seenEdges.add(edgeId);
|
|
3737
|
+
const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
|
|
3738
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
3739
|
+
noteExtractedDropped({
|
|
3740
|
+
source: service.node.id,
|
|
3741
|
+
target: ep.infraId,
|
|
3742
|
+
type: edgeType,
|
|
3743
|
+
confidence,
|
|
3744
|
+
confidenceKind: ep.confidenceKind,
|
|
3745
|
+
evidence: ep.evidence
|
|
3746
|
+
});
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3490
3749
|
if (!graph.hasEdge(edgeId)) {
|
|
3491
3750
|
const edge = {
|
|
3492
3751
|
id: edgeId,
|
|
@@ -3494,6 +3753,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3494
3753
|
target: ep.infraId,
|
|
3495
3754
|
type: edgeType,
|
|
3496
3755
|
provenance: import_types14.Provenance.EXTRACTED,
|
|
3756
|
+
confidence,
|
|
3497
3757
|
evidence: ep.evidence
|
|
3498
3758
|
};
|
|
3499
3759
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3517,7 +3777,7 @@ init_cjs_shims();
|
|
|
3517
3777
|
|
|
3518
3778
|
// src/extract/infra/docker-compose.ts
|
|
3519
3779
|
init_cjs_shims();
|
|
3520
|
-
var
|
|
3780
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3521
3781
|
var import_types16 = require("@neat.is/types");
|
|
3522
3782
|
|
|
3523
3783
|
// src/extract/infra/shared.ts
|
|
@@ -3555,7 +3815,7 @@ function dependsOnList(value) {
|
|
|
3555
3815
|
}
|
|
3556
3816
|
function serviceNameToServiceNode(name, services) {
|
|
3557
3817
|
for (const s of services) {
|
|
3558
|
-
if (s.node.name === name ||
|
|
3818
|
+
if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
|
|
3559
3819
|
}
|
|
3560
3820
|
return null;
|
|
3561
3821
|
}
|
|
@@ -3564,7 +3824,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3564
3824
|
let edgesAdded = 0;
|
|
3565
3825
|
let composePath = null;
|
|
3566
3826
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3567
|
-
const abs =
|
|
3827
|
+
const abs = import_node_path25.default.join(scanPath, name);
|
|
3568
3828
|
if (await exists(abs)) {
|
|
3569
3829
|
composePath = abs;
|
|
3570
3830
|
break;
|
|
@@ -3575,13 +3835,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3575
3835
|
try {
|
|
3576
3836
|
compose = await readYaml(composePath);
|
|
3577
3837
|
} catch (err) {
|
|
3578
|
-
|
|
3579
|
-
|
|
3838
|
+
recordExtractionError(
|
|
3839
|
+
"infra docker-compose",
|
|
3840
|
+
import_node_path25.default.relative(scanPath, composePath),
|
|
3841
|
+
err
|
|
3580
3842
|
);
|
|
3581
3843
|
return { nodesAdded, edgesAdded };
|
|
3582
3844
|
}
|
|
3583
3845
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3584
|
-
const evidenceFile =
|
|
3846
|
+
const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
|
|
3585
3847
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3586
3848
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3587
3849
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3611,6 +3873,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3611
3873
|
target: targetId,
|
|
3612
3874
|
type: import_types16.EdgeType.DEPENDS_ON,
|
|
3613
3875
|
provenance: import_types16.Provenance.EXTRACTED,
|
|
3876
|
+
confidence: (0, import_types16.confidenceForExtracted)("structural"),
|
|
3614
3877
|
evidence: { file: evidenceFile }
|
|
3615
3878
|
};
|
|
3616
3879
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3622,8 +3885,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3622
3885
|
|
|
3623
3886
|
// src/extract/infra/dockerfile.ts
|
|
3624
3887
|
init_cjs_shims();
|
|
3625
|
-
var
|
|
3626
|
-
var
|
|
3888
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3889
|
+
var import_node_fs14 = require("fs");
|
|
3627
3890
|
var import_types17 = require("@neat.is/types");
|
|
3628
3891
|
function runtimeImage(content) {
|
|
3629
3892
|
const lines = content.split("\n");
|
|
@@ -3643,14 +3906,16 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3643
3906
|
let nodesAdded = 0;
|
|
3644
3907
|
let edgesAdded = 0;
|
|
3645
3908
|
for (const service of services) {
|
|
3646
|
-
const dockerfilePath =
|
|
3909
|
+
const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
|
|
3647
3910
|
if (!await exists(dockerfilePath)) continue;
|
|
3648
3911
|
let content;
|
|
3649
3912
|
try {
|
|
3650
|
-
content = await
|
|
3913
|
+
content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
|
|
3651
3914
|
} catch (err) {
|
|
3652
|
-
|
|
3653
|
-
|
|
3915
|
+
recordExtractionError(
|
|
3916
|
+
"infra dockerfile",
|
|
3917
|
+
import_node_path26.default.relative(scanPath, dockerfilePath),
|
|
3918
|
+
err
|
|
3654
3919
|
);
|
|
3655
3920
|
continue;
|
|
3656
3921
|
}
|
|
@@ -3669,8 +3934,9 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3669
3934
|
target: node.id,
|
|
3670
3935
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3671
3936
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3937
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
3672
3938
|
evidence: {
|
|
3673
|
-
file:
|
|
3939
|
+
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
3674
3940
|
}
|
|
3675
3941
|
};
|
|
3676
3942
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3682,19 +3948,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3682
3948
|
|
|
3683
3949
|
// src/extract/infra/terraform.ts
|
|
3684
3950
|
init_cjs_shims();
|
|
3685
|
-
var
|
|
3686
|
-
var
|
|
3951
|
+
var import_node_fs15 = require("fs");
|
|
3952
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3687
3953
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3688
3954
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3689
3955
|
if (depth > max) return [];
|
|
3690
3956
|
const out = [];
|
|
3691
|
-
const entries = await
|
|
3957
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3692
3958
|
for (const entry2 of entries) {
|
|
3693
3959
|
if (entry2.isDirectory()) {
|
|
3694
3960
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
3695
|
-
out.push(...await walkTfFiles(
|
|
3961
|
+
out.push(...await walkTfFiles(import_node_path27.default.join(start, entry2.name), depth + 1, max));
|
|
3696
3962
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
3697
|
-
out.push(
|
|
3963
|
+
out.push(import_node_path27.default.join(start, entry2.name));
|
|
3698
3964
|
}
|
|
3699
3965
|
}
|
|
3700
3966
|
return out;
|
|
@@ -3703,7 +3969,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3703
3969
|
let nodesAdded = 0;
|
|
3704
3970
|
const files = await walkTfFiles(scanPath);
|
|
3705
3971
|
for (const file of files) {
|
|
3706
|
-
const content = await
|
|
3972
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3707
3973
|
RESOURCE_RE.lastIndex = 0;
|
|
3708
3974
|
let m;
|
|
3709
3975
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3721,8 +3987,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3721
3987
|
|
|
3722
3988
|
// src/extract/infra/k8s.ts
|
|
3723
3989
|
init_cjs_shims();
|
|
3724
|
-
var
|
|
3725
|
-
var
|
|
3990
|
+
var import_node_fs16 = require("fs");
|
|
3991
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3726
3992
|
var import_yaml3 = require("yaml");
|
|
3727
3993
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3728
3994
|
Service: "k8s-service",
|
|
@@ -3736,13 +4002,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3736
4002
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3737
4003
|
if (depth > max) return [];
|
|
3738
4004
|
const out = [];
|
|
3739
|
-
const entries = await
|
|
4005
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3740
4006
|
for (const entry2 of entries) {
|
|
3741
4007
|
if (entry2.isDirectory()) {
|
|
3742
4008
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3743
|
-
out.push(...await walkYamlFiles2(
|
|
3744
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3745
|
-
out.push(
|
|
4009
|
+
out.push(...await walkYamlFiles2(import_node_path28.default.join(start, entry2.name), depth + 1, max));
|
|
4010
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
|
|
4011
|
+
out.push(import_node_path28.default.join(start, entry2.name));
|
|
3746
4012
|
}
|
|
3747
4013
|
}
|
|
3748
4014
|
return out;
|
|
@@ -3751,7 +4017,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3751
4017
|
let nodesAdded = 0;
|
|
3752
4018
|
const files = await walkYamlFiles2(scanPath);
|
|
3753
4019
|
for (const file of files) {
|
|
3754
|
-
const content = await
|
|
4020
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
3755
4021
|
let docs;
|
|
3756
4022
|
try {
|
|
3757
4023
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3785,9 +4051,49 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3785
4051
|
};
|
|
3786
4052
|
}
|
|
3787
4053
|
|
|
4054
|
+
// src/extract/index.ts
|
|
4055
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4056
|
+
|
|
4057
|
+
// src/extract/retire.ts
|
|
4058
|
+
init_cjs_shims();
|
|
4059
|
+
var import_node_fs17 = require("fs");
|
|
4060
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4061
|
+
var import_types18 = require("@neat.is/types");
|
|
4062
|
+
function retireEdgesByFile(graph, file) {
|
|
4063
|
+
const normalized = file.split("\\").join("/");
|
|
4064
|
+
const toDrop = [];
|
|
4065
|
+
graph.forEachEdge((id, attrs) => {
|
|
4066
|
+
const edge = attrs;
|
|
4067
|
+
if (edge.provenance !== import_types18.Provenance.EXTRACTED) return;
|
|
4068
|
+
if (!edge.evidence?.file) return;
|
|
4069
|
+
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4070
|
+
});
|
|
4071
|
+
for (const id of toDrop) graph.dropEdge(id);
|
|
4072
|
+
return toDrop.length;
|
|
4073
|
+
}
|
|
4074
|
+
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
4075
|
+
const toDrop = [];
|
|
4076
|
+
const bases = [scanPath, ...serviceDirs];
|
|
4077
|
+
graph.forEachEdge((id, attrs) => {
|
|
4078
|
+
const edge = attrs;
|
|
4079
|
+
if (edge.provenance !== import_types18.Provenance.EXTRACTED) return;
|
|
4080
|
+
const evidenceFile = edge.evidence?.file;
|
|
4081
|
+
if (!evidenceFile) return;
|
|
4082
|
+
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
4083
|
+
if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4084
|
+
return;
|
|
4085
|
+
}
|
|
4086
|
+
const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
|
|
4087
|
+
if (!found) toDrop.push(id);
|
|
4088
|
+
});
|
|
4089
|
+
for (const id of toDrop) graph.dropEdge(id);
|
|
4090
|
+
return toDrop.length;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
3788
4093
|
// src/extract/index.ts
|
|
3789
4094
|
async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
3790
4095
|
await ensureCompatLoaded();
|
|
4096
|
+
drainExtractionErrors();
|
|
3791
4097
|
const services = await discoverServices(scanPath);
|
|
3792
4098
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
3793
4099
|
await addServiceAliases(graph, scanPath, services);
|
|
@@ -3795,12 +4101,43 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3795
4101
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
3796
4102
|
const phase4 = await addCallEdges(graph, services);
|
|
3797
4103
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
4104
|
+
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
4105
|
+
graph,
|
|
4106
|
+
scanPath,
|
|
4107
|
+
services.map((s) => s.dir)
|
|
4108
|
+
);
|
|
3798
4109
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
3799
4110
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
4111
|
+
const errorEntries = drainExtractionErrors();
|
|
4112
|
+
if (opts.errorsPath && errorEntries.length > 0) {
|
|
4113
|
+
try {
|
|
4114
|
+
await writeExtractionErrors(errorEntries, opts.errorsPath);
|
|
4115
|
+
} catch (err) {
|
|
4116
|
+
console.warn(
|
|
4117
|
+
`[neat] failed to write extraction errors to ${opts.errorsPath}: ${err.message}`
|
|
4118
|
+
);
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
const droppedEntries = drainDroppedExtracted();
|
|
4122
|
+
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4123
|
+
const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4124
|
+
try {
|
|
4125
|
+
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4126
|
+
} catch (err) {
|
|
4127
|
+
console.warn(
|
|
4128
|
+
`[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
|
|
4129
|
+
);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
3800
4132
|
const result = {
|
|
3801
4133
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3802
4134
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3803
|
-
frontiersPromoted
|
|
4135
|
+
frontiersPromoted,
|
|
4136
|
+
extractionErrors: errorEntries.length,
|
|
4137
|
+
errorEntries,
|
|
4138
|
+
ghostsRetired,
|
|
4139
|
+
extractedDropped: droppedEntries.length,
|
|
4140
|
+
droppedEntries
|
|
3804
4141
|
};
|
|
3805
4142
|
emitNeatEvent({
|
|
3806
4143
|
type: "extraction-complete",
|
|
@@ -3817,8 +4154,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3817
4154
|
|
|
3818
4155
|
// src/persist.ts
|
|
3819
4156
|
init_cjs_shims();
|
|
3820
|
-
var
|
|
3821
|
-
var
|
|
4157
|
+
var import_node_fs18 = require("fs");
|
|
4158
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
3822
4159
|
var SCHEMA_VERSION = 2;
|
|
3823
4160
|
function migrateV1ToV2(payload) {
|
|
3824
4161
|
const nodes = payload.graph.nodes;
|
|
@@ -3832,7 +4169,7 @@ function migrateV1ToV2(payload) {
|
|
|
3832
4169
|
return { ...payload, schemaVersion: 2 };
|
|
3833
4170
|
}
|
|
3834
4171
|
async function ensureDir(filePath) {
|
|
3835
|
-
await
|
|
4172
|
+
await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
|
|
3836
4173
|
}
|
|
3837
4174
|
async function saveGraphToDisk(graph, outPath) {
|
|
3838
4175
|
await ensureDir(outPath);
|
|
@@ -3842,13 +4179,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
3842
4179
|
graph: graph.export()
|
|
3843
4180
|
};
|
|
3844
4181
|
const tmp = `${outPath}.tmp`;
|
|
3845
|
-
await
|
|
3846
|
-
await
|
|
4182
|
+
await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4183
|
+
await import_node_fs18.promises.rename(tmp, outPath);
|
|
3847
4184
|
}
|
|
3848
4185
|
async function loadGraphFromDisk(graph, outPath) {
|
|
3849
4186
|
let raw;
|
|
3850
4187
|
try {
|
|
3851
|
-
raw = await
|
|
4188
|
+
raw = await import_node_fs18.promises.readFile(outPath, "utf8");
|
|
3852
4189
|
} catch (err) {
|
|
3853
4190
|
if (err.code === "ENOENT") return;
|
|
3854
4191
|
throw err;
|
|
@@ -3901,19 +4238,19 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
3901
4238
|
|
|
3902
4239
|
// src/watch.ts
|
|
3903
4240
|
init_cjs_shims();
|
|
3904
|
-
var
|
|
3905
|
-
var
|
|
4241
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
4242
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
3906
4243
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
3907
4244
|
|
|
3908
4245
|
// src/api.ts
|
|
3909
4246
|
init_cjs_shims();
|
|
3910
4247
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
3911
4248
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
3912
|
-
var
|
|
4249
|
+
var import_types21 = require("@neat.is/types");
|
|
3913
4250
|
|
|
3914
4251
|
// src/divergences.ts
|
|
3915
4252
|
init_cjs_shims();
|
|
3916
|
-
var
|
|
4253
|
+
var import_types19 = require("@neat.is/types");
|
|
3917
4254
|
function bucketKey(source, target, type) {
|
|
3918
4255
|
return `${type}|${source}|${target}`;
|
|
3919
4256
|
}
|
|
@@ -3921,25 +4258,25 @@ function bucketEdges(graph) {
|
|
|
3921
4258
|
const buckets = /* @__PURE__ */ new Map();
|
|
3922
4259
|
graph.forEachEdge((id, attrs) => {
|
|
3923
4260
|
const e = attrs;
|
|
3924
|
-
const parsed = (0,
|
|
4261
|
+
const parsed = (0, import_types19.parseEdgeId)(id);
|
|
3925
4262
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
3926
4263
|
const key = bucketKey(e.source, e.target, e.type);
|
|
3927
4264
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3928
4265
|
switch (provenance) {
|
|
3929
|
-
case
|
|
4266
|
+
case import_types19.Provenance.EXTRACTED:
|
|
3930
4267
|
cur.extracted = e;
|
|
3931
4268
|
break;
|
|
3932
|
-
case
|
|
4269
|
+
case import_types19.Provenance.OBSERVED:
|
|
3933
4270
|
cur.observed = e;
|
|
3934
4271
|
break;
|
|
3935
|
-
case
|
|
4272
|
+
case import_types19.Provenance.INFERRED:
|
|
3936
4273
|
cur.inferred = e;
|
|
3937
4274
|
break;
|
|
3938
|
-
case
|
|
4275
|
+
case import_types19.Provenance.FRONTIER:
|
|
3939
4276
|
cur.frontier = e;
|
|
3940
4277
|
break;
|
|
3941
4278
|
default:
|
|
3942
|
-
if (e.provenance ===
|
|
4279
|
+
if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
|
|
3943
4280
|
}
|
|
3944
4281
|
buckets.set(key, cur);
|
|
3945
4282
|
});
|
|
@@ -3948,15 +4285,7 @@ function bucketEdges(graph) {
|
|
|
3948
4285
|
function nodeIsFrontier(graph, nodeId) {
|
|
3949
4286
|
if (!graph.hasNode(nodeId)) return false;
|
|
3950
4287
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
3951
|
-
return attrs.type ===
|
|
3952
|
-
}
|
|
3953
|
-
function hasAnyObservedFromSource(graph, sourceId) {
|
|
3954
|
-
if (!graph.hasNode(sourceId)) return false;
|
|
3955
|
-
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
3956
|
-
const e = graph.getEdgeAttributes(edgeId);
|
|
3957
|
-
if (e.provenance === import_types18.Provenance.OBSERVED) return true;
|
|
3958
|
-
}
|
|
3959
|
-
return false;
|
|
4288
|
+
return attrs.type === import_types19.NodeType.FrontierNode;
|
|
3960
4289
|
}
|
|
3961
4290
|
function clampConfidence(n) {
|
|
3962
4291
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -3971,32 +4300,34 @@ function reasonForMissingExtracted(source, target, type) {
|
|
|
3971
4300
|
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
3972
4301
|
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
3973
4302
|
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
4303
|
+
function gradedConfidence(edge) {
|
|
4304
|
+
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
4305
|
+
return clampConfidence(confidenceForEdge(edge));
|
|
4306
|
+
}
|
|
3974
4307
|
function detectMissingDivergences(graph, bucket) {
|
|
3975
4308
|
const out = [];
|
|
3976
4309
|
if (bucket.extracted && !bucket.observed) {
|
|
3977
4310
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
3978
|
-
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
3979
4311
|
out.push({
|
|
3980
4312
|
type: "missing-observed",
|
|
3981
4313
|
source: bucket.source,
|
|
3982
4314
|
target: bucket.target,
|
|
3983
4315
|
edgeType: bucket.type,
|
|
3984
4316
|
extracted: bucket.extracted,
|
|
3985
|
-
confidence:
|
|
4317
|
+
confidence: gradedConfidence(bucket.extracted),
|
|
3986
4318
|
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
3987
4319
|
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
3988
4320
|
});
|
|
3989
4321
|
}
|
|
3990
4322
|
}
|
|
3991
4323
|
if (bucket.observed && !bucket.extracted) {
|
|
3992
|
-
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
3993
4324
|
out.push({
|
|
3994
4325
|
type: "missing-extracted",
|
|
3995
4326
|
source: bucket.source,
|
|
3996
4327
|
target: bucket.target,
|
|
3997
4328
|
edgeType: bucket.type,
|
|
3998
4329
|
observed: bucket.observed,
|
|
3999
|
-
confidence:
|
|
4330
|
+
confidence: gradedConfidence(bucket.observed),
|
|
4000
4331
|
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4001
4332
|
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4002
4333
|
});
|
|
@@ -4015,7 +4346,7 @@ function declaredHostFor(svc) {
|
|
|
4015
4346
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4016
4347
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4017
4348
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4018
|
-
if (e.type ===
|
|
4349
|
+
if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
|
|
4019
4350
|
return true;
|
|
4020
4351
|
}
|
|
4021
4352
|
}
|
|
@@ -4028,10 +4359,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4028
4359
|
const out = [];
|
|
4029
4360
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4030
4361
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4031
|
-
if (edge.type !==
|
|
4032
|
-
if (edge.provenance !==
|
|
4362
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4363
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
4033
4364
|
const target = graph.getNodeAttributes(edge.target);
|
|
4034
|
-
if (target.type !==
|
|
4365
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
4035
4366
|
const observedHost = target.host?.trim();
|
|
4036
4367
|
if (!observedHost) continue;
|
|
4037
4368
|
if (observedHost === declaredHost) continue;
|
|
@@ -4053,10 +4384,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4053
4384
|
const deps = svc.dependencies ?? {};
|
|
4054
4385
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4055
4386
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4056
|
-
if (edge.type !==
|
|
4057
|
-
if (edge.provenance !==
|
|
4387
|
+
if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
|
|
4388
|
+
if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
|
|
4058
4389
|
const target = graph.getNodeAttributes(edge.target);
|
|
4059
|
-
if (target.type !==
|
|
4390
|
+
if (target.type !== import_types19.NodeType.DatabaseNode) continue;
|
|
4060
4391
|
for (const pair of compatPairs()) {
|
|
4061
4392
|
if (pair.engine !== target.engine) continue;
|
|
4062
4393
|
const declared = deps[pair.driver];
|
|
@@ -4117,7 +4448,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4117
4448
|
}
|
|
4118
4449
|
graph.forEachNode((nodeId, attrs) => {
|
|
4119
4450
|
const n = attrs;
|
|
4120
|
-
if (n.type !==
|
|
4451
|
+
if (n.type !== import_types19.NodeType.ServiceNode) return;
|
|
4121
4452
|
const svc = n;
|
|
4122
4453
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4123
4454
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4135,13 +4466,22 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4135
4466
|
const target = opts.node;
|
|
4136
4467
|
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4137
4468
|
}
|
|
4469
|
+
const TYPE_LEADERSHIP = {
|
|
4470
|
+
"missing-extracted": 0,
|
|
4471
|
+
"missing-observed": 1,
|
|
4472
|
+
"version-mismatch": 2,
|
|
4473
|
+
"host-mismatch": 3,
|
|
4474
|
+
"compat-violation": 4
|
|
4475
|
+
};
|
|
4138
4476
|
filtered.sort((a, b) => {
|
|
4139
4477
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4478
|
+
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
4479
|
+
if (lead !== 0) return lead;
|
|
4140
4480
|
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4141
4481
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4142
4482
|
return a.target.localeCompare(b.target);
|
|
4143
4483
|
});
|
|
4144
|
-
return
|
|
4484
|
+
return import_types19.DivergenceResultSchema.parse({
|
|
4145
4485
|
divergences: filtered,
|
|
4146
4486
|
totalAffected: filtered.length,
|
|
4147
4487
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4150,7 +4490,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4150
4490
|
|
|
4151
4491
|
// src/diff.ts
|
|
4152
4492
|
init_cjs_shims();
|
|
4153
|
-
var
|
|
4493
|
+
var import_node_fs19 = require("fs");
|
|
4154
4494
|
async function loadSnapshotForDiff(target) {
|
|
4155
4495
|
if (/^https?:\/\//i.test(target)) {
|
|
4156
4496
|
const res = await fetch(target);
|
|
@@ -4159,7 +4499,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4159
4499
|
}
|
|
4160
4500
|
return await res.json();
|
|
4161
4501
|
}
|
|
4162
|
-
const raw = await
|
|
4502
|
+
const raw = await import_node_fs19.promises.readFile(target, "utf8");
|
|
4163
4503
|
return JSON.parse(raw);
|
|
4164
4504
|
}
|
|
4165
4505
|
function indexEntries(entries) {
|
|
@@ -4227,23 +4567,23 @@ function canonicalJson(value) {
|
|
|
4227
4567
|
|
|
4228
4568
|
// src/projects.ts
|
|
4229
4569
|
init_cjs_shims();
|
|
4230
|
-
var
|
|
4570
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4231
4571
|
function pathsForProject(project, baseDir) {
|
|
4232
4572
|
if (project === DEFAULT_PROJECT) {
|
|
4233
4573
|
return {
|
|
4234
|
-
snapshotPath:
|
|
4235
|
-
errorsPath:
|
|
4236
|
-
staleEventsPath:
|
|
4237
|
-
embeddingsCachePath:
|
|
4238
|
-
policyViolationsPath:
|
|
4574
|
+
snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
|
|
4575
|
+
errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
|
|
4576
|
+
staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
|
|
4577
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
|
|
4578
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
|
|
4239
4579
|
};
|
|
4240
4580
|
}
|
|
4241
4581
|
return {
|
|
4242
|
-
snapshotPath:
|
|
4243
|
-
errorsPath:
|
|
4244
|
-
staleEventsPath:
|
|
4245
|
-
embeddingsCachePath:
|
|
4246
|
-
policyViolationsPath:
|
|
4582
|
+
snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
|
|
4583
|
+
errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
|
|
4584
|
+
staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4585
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
|
|
4586
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4247
4587
|
};
|
|
4248
4588
|
}
|
|
4249
4589
|
var Projects = class {
|
|
@@ -4279,49 +4619,49 @@ var Projects = class {
|
|
|
4279
4619
|
|
|
4280
4620
|
// src/registry.ts
|
|
4281
4621
|
init_cjs_shims();
|
|
4282
|
-
var
|
|
4622
|
+
var import_node_fs20 = require("fs");
|
|
4283
4623
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4284
|
-
var
|
|
4285
|
-
var
|
|
4624
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4625
|
+
var import_types20 = require("@neat.is/types");
|
|
4286
4626
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4287
4627
|
var LOCK_RETRY_MS = 50;
|
|
4288
4628
|
function neatHome() {
|
|
4289
4629
|
const override = process.env.NEAT_HOME;
|
|
4290
|
-
if (override && override.length > 0) return
|
|
4291
|
-
return
|
|
4630
|
+
if (override && override.length > 0) return import_node_path33.default.resolve(override);
|
|
4631
|
+
return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
|
|
4292
4632
|
}
|
|
4293
4633
|
function registryPath() {
|
|
4294
|
-
return
|
|
4634
|
+
return import_node_path33.default.join(neatHome(), "projects.json");
|
|
4295
4635
|
}
|
|
4296
4636
|
function registryLockPath() {
|
|
4297
|
-
return
|
|
4637
|
+
return import_node_path33.default.join(neatHome(), "projects.json.lock");
|
|
4298
4638
|
}
|
|
4299
4639
|
async function normalizeProjectPath(input) {
|
|
4300
|
-
const resolved =
|
|
4640
|
+
const resolved = import_node_path33.default.resolve(input);
|
|
4301
4641
|
try {
|
|
4302
|
-
return await
|
|
4642
|
+
return await import_node_fs20.promises.realpath(resolved);
|
|
4303
4643
|
} catch {
|
|
4304
4644
|
return resolved;
|
|
4305
4645
|
}
|
|
4306
4646
|
}
|
|
4307
4647
|
async function writeAtomically(target, contents) {
|
|
4308
|
-
await
|
|
4648
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(target), { recursive: true });
|
|
4309
4649
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4310
|
-
const fd = await
|
|
4650
|
+
const fd = await import_node_fs20.promises.open(tmp, "w");
|
|
4311
4651
|
try {
|
|
4312
4652
|
await fd.writeFile(contents, "utf8");
|
|
4313
4653
|
await fd.sync();
|
|
4314
4654
|
} finally {
|
|
4315
4655
|
await fd.close();
|
|
4316
4656
|
}
|
|
4317
|
-
await
|
|
4657
|
+
await import_node_fs20.promises.rename(tmp, target);
|
|
4318
4658
|
}
|
|
4319
4659
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
4320
4660
|
const deadline = Date.now() + timeoutMs;
|
|
4321
|
-
await
|
|
4661
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(lockPath), { recursive: true });
|
|
4322
4662
|
while (true) {
|
|
4323
4663
|
try {
|
|
4324
|
-
const fd = await
|
|
4664
|
+
const fd = await import_node_fs20.promises.open(lockPath, "wx");
|
|
4325
4665
|
await fd.close();
|
|
4326
4666
|
return;
|
|
4327
4667
|
} catch (err) {
|
|
@@ -4337,7 +4677,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
4337
4677
|
}
|
|
4338
4678
|
}
|
|
4339
4679
|
async function releaseLock(lockPath) {
|
|
4340
|
-
await
|
|
4680
|
+
await import_node_fs20.promises.unlink(lockPath).catch(() => {
|
|
4341
4681
|
});
|
|
4342
4682
|
}
|
|
4343
4683
|
async function withLock(fn) {
|
|
@@ -4353,7 +4693,7 @@ async function readRegistry() {
|
|
|
4353
4693
|
const file = registryPath();
|
|
4354
4694
|
let raw;
|
|
4355
4695
|
try {
|
|
4356
|
-
raw = await
|
|
4696
|
+
raw = await import_node_fs20.promises.readFile(file, "utf8");
|
|
4357
4697
|
} catch (err) {
|
|
4358
4698
|
if (err.code === "ENOENT") {
|
|
4359
4699
|
return { version: 1, projects: [] };
|
|
@@ -4361,10 +4701,10 @@ async function readRegistry() {
|
|
|
4361
4701
|
throw err;
|
|
4362
4702
|
}
|
|
4363
4703
|
const parsed = JSON.parse(raw);
|
|
4364
|
-
return
|
|
4704
|
+
return import_types20.RegistryFileSchema.parse(parsed);
|
|
4365
4705
|
}
|
|
4366
4706
|
async function writeRegistry(reg) {
|
|
4367
|
-
const validated =
|
|
4707
|
+
const validated = import_types20.RegistryFileSchema.parse(reg);
|
|
4368
4708
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4369
4709
|
}
|
|
4370
4710
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -4615,11 +4955,11 @@ function registerRoutes(scope, ctx) {
|
|
|
4615
4955
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4616
4956
|
const parsed = [];
|
|
4617
4957
|
for (const c of candidates) {
|
|
4618
|
-
const r =
|
|
4958
|
+
const r = import_types21.DivergenceTypeSchema.safeParse(c);
|
|
4619
4959
|
if (!r.success) {
|
|
4620
4960
|
return reply.code(400).send({
|
|
4621
4961
|
error: `unknown divergence type "${c}"`,
|
|
4622
|
-
allowed:
|
|
4962
|
+
allowed: import_types21.DivergenceTypeSchema.options
|
|
4623
4963
|
});
|
|
4624
4964
|
}
|
|
4625
4965
|
parsed.push(r.data);
|
|
@@ -4803,7 +5143,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4803
5143
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4804
5144
|
let violations = await log.readAll();
|
|
4805
5145
|
if (req.query.severity) {
|
|
4806
|
-
const sev =
|
|
5146
|
+
const sev = import_types21.PolicySeveritySchema.safeParse(req.query.severity);
|
|
4807
5147
|
if (!sev.success) {
|
|
4808
5148
|
return reply.code(400).send({
|
|
4809
5149
|
error: "invalid severity",
|
|
@@ -4820,7 +5160,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4820
5160
|
scope.post("/policies/check", async (req, reply) => {
|
|
4821
5161
|
const proj = resolveProject(registry, req, reply);
|
|
4822
5162
|
if (!proj) return;
|
|
4823
|
-
const parsed =
|
|
5163
|
+
const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
4824
5164
|
if (!parsed.success) {
|
|
4825
5165
|
return reply.code(400).send({
|
|
4826
5166
|
error: "invalid /policies/check body",
|
|
@@ -4918,30 +5258,14 @@ async function buildApi(opts) {
|
|
|
4918
5258
|
return app;
|
|
4919
5259
|
}
|
|
4920
5260
|
|
|
4921
|
-
// src/extract/retire.ts
|
|
4922
|
-
init_cjs_shims();
|
|
4923
|
-
var import_types21 = require("@neat.is/types");
|
|
4924
|
-
function retireEdgesByFile(graph, file) {
|
|
4925
|
-
const normalized = file.split("\\").join("/");
|
|
4926
|
-
const toDrop = [];
|
|
4927
|
-
graph.forEachEdge((id, attrs) => {
|
|
4928
|
-
const edge = attrs;
|
|
4929
|
-
if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
|
|
4930
|
-
if (!edge.evidence?.file) return;
|
|
4931
|
-
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4932
|
-
});
|
|
4933
|
-
for (const id of toDrop) graph.dropEdge(id);
|
|
4934
|
-
return toDrop.length;
|
|
4935
|
-
}
|
|
4936
|
-
|
|
4937
5261
|
// src/watch.ts
|
|
4938
5262
|
init_otel();
|
|
4939
5263
|
init_otel_grpc();
|
|
4940
5264
|
|
|
4941
5265
|
// src/search.ts
|
|
4942
5266
|
init_cjs_shims();
|
|
4943
|
-
var
|
|
4944
|
-
var
|
|
5267
|
+
var import_node_fs21 = require("fs");
|
|
5268
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
4945
5269
|
var import_node_crypto = require("crypto");
|
|
4946
5270
|
var DEFAULT_LIMIT = 10;
|
|
4947
5271
|
var NOMIC_DIM = 768;
|
|
@@ -5071,7 +5395,7 @@ async function pickEmbedder() {
|
|
|
5071
5395
|
}
|
|
5072
5396
|
async function readCache(cachePath) {
|
|
5073
5397
|
try {
|
|
5074
|
-
const raw = await
|
|
5398
|
+
const raw = await import_node_fs21.promises.readFile(cachePath, "utf8");
|
|
5075
5399
|
const parsed = JSON.parse(raw);
|
|
5076
5400
|
if (parsed.version !== 1) return null;
|
|
5077
5401
|
return parsed;
|
|
@@ -5080,8 +5404,8 @@ async function readCache(cachePath) {
|
|
|
5080
5404
|
}
|
|
5081
5405
|
}
|
|
5082
5406
|
async function writeCache(cachePath, cache) {
|
|
5083
|
-
await
|
|
5084
|
-
await
|
|
5407
|
+
await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(cachePath), { recursive: true });
|
|
5408
|
+
await import_node_fs21.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
5085
5409
|
}
|
|
5086
5410
|
var VectorIndex = class {
|
|
5087
5411
|
constructor(embedder, cachePath) {
|
|
@@ -5236,8 +5560,8 @@ var ALL_PHASES = [
|
|
|
5236
5560
|
];
|
|
5237
5561
|
function classifyChange(relPath) {
|
|
5238
5562
|
const phases = /* @__PURE__ */ new Set();
|
|
5239
|
-
const base =
|
|
5240
|
-
const segments = relPath.split(
|
|
5563
|
+
const base = import_node_path37.default.basename(relPath).toLowerCase();
|
|
5564
|
+
const segments = relPath.split(import_node_path37.default.sep).map((s) => s.toLowerCase());
|
|
5241
5565
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
5242
5566
|
phases.add("services");
|
|
5243
5567
|
phases.add("aliases");
|
|
@@ -5332,16 +5656,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
5332
5656
|
if (count >= limit) return;
|
|
5333
5657
|
let entries;
|
|
5334
5658
|
try {
|
|
5335
|
-
entries =
|
|
5659
|
+
entries = import_node_fs22.default.readdirSync(dir, { withFileTypes: true });
|
|
5336
5660
|
} catch {
|
|
5337
5661
|
return;
|
|
5338
5662
|
}
|
|
5339
5663
|
for (const e of entries) {
|
|
5340
5664
|
if (count >= limit) return;
|
|
5341
5665
|
if (!e.isDirectory()) continue;
|
|
5342
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
5666
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path37.default.join(dir, e.name) + import_node_path37.default.sep))) continue;
|
|
5343
5667
|
count++;
|
|
5344
|
-
if (depth < 2) visit(
|
|
5668
|
+
if (depth < 2) visit(import_node_path37.default.join(dir, e.name), depth + 1);
|
|
5345
5669
|
}
|
|
5346
5670
|
};
|
|
5347
5671
|
visit(scanPath, 0);
|
|
@@ -5359,8 +5683,8 @@ async function startWatch(graph, opts) {
|
|
|
5359
5683
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
5360
5684
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
5361
5685
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
5362
|
-
const policyFilePath =
|
|
5363
|
-
const policyViolationsPath =
|
|
5686
|
+
const policyFilePath = import_node_path37.default.join(opts.scanPath, "policy.json");
|
|
5687
|
+
const policyViolationsPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
5364
5688
|
let policies = [];
|
|
5365
5689
|
try {
|
|
5366
5690
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -5409,7 +5733,7 @@ async function startWatch(graph, opts) {
|
|
|
5409
5733
|
const host = opts.host ?? "0.0.0.0";
|
|
5410
5734
|
const port = opts.port ?? 8080;
|
|
5411
5735
|
const otelPort = opts.otelPort ?? 4318;
|
|
5412
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
5736
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "embeddings.json");
|
|
5413
5737
|
let searchIndex;
|
|
5414
5738
|
try {
|
|
5415
5739
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -5427,7 +5751,7 @@ async function startWatch(graph, opts) {
|
|
|
5427
5751
|
// Paths are derived from the explicit options the watch caller passes
|
|
5428
5752
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
5429
5753
|
// fields so the registry shape is complete.
|
|
5430
|
-
...pathsForProject(projectName,
|
|
5754
|
+
...pathsForProject(projectName, import_node_path37.default.dirname(opts.outPath)),
|
|
5431
5755
|
snapshotPath: opts.outPath,
|
|
5432
5756
|
errorsPath: opts.errorsPath,
|
|
5433
5757
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -5512,9 +5836,9 @@ async function startWatch(graph, opts) {
|
|
|
5512
5836
|
};
|
|
5513
5837
|
const onPath = (absPath) => {
|
|
5514
5838
|
if (shouldIgnore(absPath)) return;
|
|
5515
|
-
const rel =
|
|
5839
|
+
const rel = import_node_path37.default.relative(opts.scanPath, absPath);
|
|
5516
5840
|
if (!rel || rel.startsWith("..")) return;
|
|
5517
|
-
pendingPaths.add(rel.split(
|
|
5841
|
+
pendingPaths.add(rel.split(import_node_path37.default.sep).join("/"));
|
|
5518
5842
|
const phases = classifyChange(rel);
|
|
5519
5843
|
if (phases.size === 0) {
|
|
5520
5844
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -5571,8 +5895,8 @@ init_cjs_shims();
|
|
|
5571
5895
|
|
|
5572
5896
|
// src/installers/javascript.ts
|
|
5573
5897
|
init_cjs_shims();
|
|
5574
|
-
var
|
|
5575
|
-
var
|
|
5898
|
+
var import_node_fs23 = require("fs");
|
|
5899
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5576
5900
|
var SDK_PACKAGES = [
|
|
5577
5901
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
5578
5902
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
@@ -5588,7 +5912,7 @@ var OTEL_ENV = {
|
|
|
5588
5912
|
};
|
|
5589
5913
|
async function readPackageJson(serviceDir) {
|
|
5590
5914
|
try {
|
|
5591
|
-
const raw = await
|
|
5915
|
+
const raw = await import_node_fs23.promises.readFile(import_node_path38.default.join(serviceDir, "package.json"), "utf8");
|
|
5592
5916
|
return JSON.parse(raw);
|
|
5593
5917
|
} catch {
|
|
5594
5918
|
return null;
|
|
@@ -5607,7 +5931,7 @@ function rewriteStartScript(start) {
|
|
|
5607
5931
|
}
|
|
5608
5932
|
async function plan(serviceDir) {
|
|
5609
5933
|
const pkg = await readPackageJson(serviceDir);
|
|
5610
|
-
const manifestPath =
|
|
5934
|
+
const manifestPath = import_node_path38.default.join(serviceDir, "package.json");
|
|
5611
5935
|
const empty = {
|
|
5612
5936
|
language: "javascript",
|
|
5613
5937
|
serviceDir,
|
|
@@ -5654,7 +5978,7 @@ async function apply(installPlan) {
|
|
|
5654
5978
|
const originals = /* @__PURE__ */ new Map();
|
|
5655
5979
|
for (const file of touched) {
|
|
5656
5980
|
try {
|
|
5657
|
-
originals.set(file, await
|
|
5981
|
+
originals.set(file, await import_node_fs23.promises.readFile(file, "utf8"));
|
|
5658
5982
|
} catch {
|
|
5659
5983
|
}
|
|
5660
5984
|
}
|
|
@@ -5680,8 +6004,8 @@ async function apply(installPlan) {
|
|
|
5680
6004
|
}
|
|
5681
6005
|
const newRaw = JSON.stringify(pkg, null, 2) + "\n";
|
|
5682
6006
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
5683
|
-
await
|
|
5684
|
-
await
|
|
6007
|
+
await import_node_fs23.promises.writeFile(tmp, newRaw, "utf8");
|
|
6008
|
+
await import_node_fs23.promises.rename(tmp, file);
|
|
5685
6009
|
}
|
|
5686
6010
|
} catch (err) {
|
|
5687
6011
|
await rollback(installPlan, originals);
|
|
@@ -5692,7 +6016,7 @@ async function rollback(installPlan, originals) {
|
|
|
5692
6016
|
const restored = [];
|
|
5693
6017
|
for (const [file, raw] of originals.entries()) {
|
|
5694
6018
|
try {
|
|
5695
|
-
await
|
|
6019
|
+
await import_node_fs23.promises.writeFile(file, raw, "utf8");
|
|
5696
6020
|
restored.push(file);
|
|
5697
6021
|
} catch {
|
|
5698
6022
|
}
|
|
@@ -5706,8 +6030,8 @@ async function rollback(installPlan, originals) {
|
|
|
5706
6030
|
...restored.map((f) => `restored: ${f}`),
|
|
5707
6031
|
""
|
|
5708
6032
|
];
|
|
5709
|
-
const rollbackPath =
|
|
5710
|
-
await
|
|
6033
|
+
const rollbackPath = import_node_path38.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
6034
|
+
await import_node_fs23.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
5711
6035
|
}
|
|
5712
6036
|
var javascriptInstaller = {
|
|
5713
6037
|
name: "javascript",
|
|
@@ -5718,8 +6042,8 @@ var javascriptInstaller = {
|
|
|
5718
6042
|
|
|
5719
6043
|
// src/installers/python.ts
|
|
5720
6044
|
init_cjs_shims();
|
|
5721
|
-
var
|
|
5722
|
-
var
|
|
6045
|
+
var import_node_fs24 = require("fs");
|
|
6046
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
5723
6047
|
var SDK_PACKAGES2 = [
|
|
5724
6048
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
5725
6049
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -5731,7 +6055,7 @@ var OTEL_ENV2 = {
|
|
|
5731
6055
|
};
|
|
5732
6056
|
async function exists2(p) {
|
|
5733
6057
|
try {
|
|
5734
|
-
await
|
|
6058
|
+
await import_node_fs24.promises.stat(p);
|
|
5735
6059
|
return true;
|
|
5736
6060
|
} catch {
|
|
5737
6061
|
return false;
|
|
@@ -5740,7 +6064,7 @@ async function exists2(p) {
|
|
|
5740
6064
|
async function detect2(serviceDir) {
|
|
5741
6065
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
5742
6066
|
for (const m of markers) {
|
|
5743
|
-
if (await exists2(
|
|
6067
|
+
if (await exists2(import_node_path39.default.join(serviceDir, m))) return true;
|
|
5744
6068
|
}
|
|
5745
6069
|
return false;
|
|
5746
6070
|
}
|
|
@@ -5750,9 +6074,9 @@ function reqPackageName(line) {
|
|
|
5750
6074
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
5751
6075
|
}
|
|
5752
6076
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
5753
|
-
const file =
|
|
6077
|
+
const file = import_node_path39.default.join(serviceDir, "requirements.txt");
|
|
5754
6078
|
if (!await exists2(file)) return null;
|
|
5755
|
-
const raw = await
|
|
6079
|
+
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
5756
6080
|
const presentNames = new Set(
|
|
5757
6081
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
5758
6082
|
);
|
|
@@ -5760,9 +6084,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
5760
6084
|
return { manifest: file, missing: [...missing] };
|
|
5761
6085
|
}
|
|
5762
6086
|
async function planProcfileEdits(serviceDir) {
|
|
5763
|
-
const procfile =
|
|
6087
|
+
const procfile = import_node_path39.default.join(serviceDir, "Procfile");
|
|
5764
6088
|
if (!await exists2(procfile)) return [];
|
|
5765
|
-
const raw = await
|
|
6089
|
+
const raw = await import_node_fs24.promises.readFile(procfile, "utf8");
|
|
5766
6090
|
const edits = [];
|
|
5767
6091
|
for (const line of raw.split(/\r?\n/)) {
|
|
5768
6092
|
if (line.length === 0) continue;
|
|
@@ -5814,8 +6138,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
5814
6138
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
5815
6139
|
`;
|
|
5816
6140
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
5817
|
-
await
|
|
5818
|
-
await
|
|
6141
|
+
await import_node_fs24.promises.writeFile(tmp, next, "utf8");
|
|
6142
|
+
await import_node_fs24.promises.rename(tmp, manifest);
|
|
5819
6143
|
}
|
|
5820
6144
|
async function applyProcfile(procfile, edits, original) {
|
|
5821
6145
|
let next = original;
|
|
@@ -5824,8 +6148,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
5824
6148
|
next = next.replace(e.before, e.after);
|
|
5825
6149
|
}
|
|
5826
6150
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
5827
|
-
await
|
|
5828
|
-
await
|
|
6151
|
+
await import_node_fs24.promises.writeFile(tmp, next, "utf8");
|
|
6152
|
+
await import_node_fs24.promises.rename(tmp, procfile);
|
|
5829
6153
|
}
|
|
5830
6154
|
async function apply2(installPlan) {
|
|
5831
6155
|
const touched = /* @__PURE__ */ new Set();
|
|
@@ -5835,7 +6159,7 @@ async function apply2(installPlan) {
|
|
|
5835
6159
|
const originals = /* @__PURE__ */ new Map();
|
|
5836
6160
|
for (const file of touched) {
|
|
5837
6161
|
try {
|
|
5838
|
-
originals.set(file, await
|
|
6162
|
+
originals.set(file, await import_node_fs24.promises.readFile(file, "utf8"));
|
|
5839
6163
|
} catch {
|
|
5840
6164
|
}
|
|
5841
6165
|
}
|
|
@@ -5845,7 +6169,7 @@ async function apply2(installPlan) {
|
|
|
5845
6169
|
if (raw === void 0) {
|
|
5846
6170
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
5847
6171
|
}
|
|
5848
|
-
const base =
|
|
6172
|
+
const base = import_node_path39.default.basename(file);
|
|
5849
6173
|
if (base === "requirements.txt") {
|
|
5850
6174
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
5851
6175
|
if (edits.length > 0) await applyRequirementsTxt(file, edits, raw);
|
|
@@ -5863,7 +6187,7 @@ async function rollback2(installPlan, originals) {
|
|
|
5863
6187
|
const restored = [];
|
|
5864
6188
|
for (const [file, raw] of originals.entries()) {
|
|
5865
6189
|
try {
|
|
5866
|
-
await
|
|
6190
|
+
await import_node_fs24.promises.writeFile(file, raw, "utf8");
|
|
5867
6191
|
restored.push(file);
|
|
5868
6192
|
} catch {
|
|
5869
6193
|
}
|
|
@@ -5877,8 +6201,8 @@ async function rollback2(installPlan, originals) {
|
|
|
5877
6201
|
...restored.map((f) => `restored: ${f}`),
|
|
5878
6202
|
""
|
|
5879
6203
|
];
|
|
5880
|
-
const rollbackPath =
|
|
5881
|
-
await
|
|
6204
|
+
const rollbackPath = import_node_path39.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
6205
|
+
await import_node_fs24.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
5882
6206
|
}
|
|
5883
6207
|
var pythonInstaller = {
|
|
5884
6208
|
name: "python",
|
|
@@ -5991,10 +6315,10 @@ var TransportError = class extends Error {
|
|
|
5991
6315
|
function createHttpClient(baseUrl) {
|
|
5992
6316
|
const root = baseUrl.replace(/\/$/, "");
|
|
5993
6317
|
return {
|
|
5994
|
-
async get(
|
|
6318
|
+
async get(path41) {
|
|
5995
6319
|
let res;
|
|
5996
6320
|
try {
|
|
5997
|
-
res = await fetch(`${root}${
|
|
6321
|
+
res = await fetch(`${root}${path41}`);
|
|
5998
6322
|
} catch (err) {
|
|
5999
6323
|
throw new TransportError(
|
|
6000
6324
|
`cannot reach neat-core at ${root}: ${err.message}`
|
|
@@ -6004,16 +6328,16 @@ function createHttpClient(baseUrl) {
|
|
|
6004
6328
|
const body = await res.text().catch(() => "");
|
|
6005
6329
|
throw new HttpError(
|
|
6006
6330
|
res.status,
|
|
6007
|
-
`${res.status} ${res.statusText} on GET ${
|
|
6331
|
+
`${res.status} ${res.statusText} on GET ${path41}: ${body}`,
|
|
6008
6332
|
body
|
|
6009
6333
|
);
|
|
6010
6334
|
}
|
|
6011
6335
|
return await res.json();
|
|
6012
6336
|
},
|
|
6013
|
-
async post(
|
|
6337
|
+
async post(path41, body) {
|
|
6014
6338
|
let res;
|
|
6015
6339
|
try {
|
|
6016
|
-
res = await fetch(`${root}${
|
|
6340
|
+
res = await fetch(`${root}${path41}`, {
|
|
6017
6341
|
method: "POST",
|
|
6018
6342
|
headers: { "content-type": "application/json" },
|
|
6019
6343
|
body: JSON.stringify(body)
|
|
@@ -6027,7 +6351,7 @@ function createHttpClient(baseUrl) {
|
|
|
6027
6351
|
const text = await res.text().catch(() => "");
|
|
6028
6352
|
throw new HttpError(
|
|
6029
6353
|
res.status,
|
|
6030
|
-
`${res.status} ${res.statusText} on POST ${
|
|
6354
|
+
`${res.status} ${res.statusText} on POST ${path41}: ${text}`,
|
|
6031
6355
|
text
|
|
6032
6356
|
);
|
|
6033
6357
|
}
|
|
@@ -6041,12 +6365,12 @@ function projectPath(project, suffix) {
|
|
|
6041
6365
|
}
|
|
6042
6366
|
async function runRootCause(client, input) {
|
|
6043
6367
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
6044
|
-
const
|
|
6368
|
+
const path41 = projectPath(
|
|
6045
6369
|
input.project,
|
|
6046
6370
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
6047
6371
|
);
|
|
6048
6372
|
try {
|
|
6049
|
-
const result = await client.get(
|
|
6373
|
+
const result = await client.get(path41);
|
|
6050
6374
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
6051
6375
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
6052
6376
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -6072,12 +6396,12 @@ async function runRootCause(client, input) {
|
|
|
6072
6396
|
}
|
|
6073
6397
|
async function runBlastRadius(client, input) {
|
|
6074
6398
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
6075
|
-
const
|
|
6399
|
+
const path41 = projectPath(
|
|
6076
6400
|
input.project,
|
|
6077
6401
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
6078
6402
|
);
|
|
6079
6403
|
try {
|
|
6080
|
-
const result = await client.get(
|
|
6404
|
+
const result = await client.get(path41);
|
|
6081
6405
|
if (result.totalAffected === 0) {
|
|
6082
6406
|
return {
|
|
6083
6407
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -6111,12 +6435,12 @@ function formatBlastEntry(n) {
|
|
|
6111
6435
|
}
|
|
6112
6436
|
async function runDependencies(client, input) {
|
|
6113
6437
|
const depth = input.depth ?? 3;
|
|
6114
|
-
const
|
|
6438
|
+
const path41 = projectPath(
|
|
6115
6439
|
input.project,
|
|
6116
6440
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
6117
6441
|
);
|
|
6118
6442
|
try {
|
|
6119
|
-
const result = await client.get(
|
|
6443
|
+
const result = await client.get(path41);
|
|
6120
6444
|
if (result.total === 0) {
|
|
6121
6445
|
return {
|
|
6122
6446
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -6197,9 +6521,9 @@ function formatDuration(ms) {
|
|
|
6197
6521
|
return `${Math.round(h / 24)}d`;
|
|
6198
6522
|
}
|
|
6199
6523
|
async function runIncidents(client, input) {
|
|
6200
|
-
const
|
|
6524
|
+
const path41 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
6201
6525
|
try {
|
|
6202
|
-
const body = await client.get(
|
|
6526
|
+
const body = await client.get(path41);
|
|
6203
6527
|
const events = body.events;
|
|
6204
6528
|
if (events.length === 0) {
|
|
6205
6529
|
return {
|
|
@@ -6714,7 +7038,7 @@ async function buildPatchSections(services) {
|
|
|
6714
7038
|
}
|
|
6715
7039
|
async function runInit(opts) {
|
|
6716
7040
|
const written = [];
|
|
6717
|
-
const stat = await
|
|
7041
|
+
const stat = await import_node_fs25.promises.stat(opts.scanPath).catch(() => null);
|
|
6718
7042
|
if (!stat || !stat.isDirectory()) {
|
|
6719
7043
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
6720
7044
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -6723,9 +7047,9 @@ async function runInit(opts) {
|
|
|
6723
7047
|
printDiscoveryReport(opts, services);
|
|
6724
7048
|
const sections = opts.noInstall ? [] : await buildPatchSections(services);
|
|
6725
7049
|
const patch = renderPatch(sections);
|
|
6726
|
-
const patchPath =
|
|
7050
|
+
const patchPath = import_node_path40.default.join(opts.scanPath, "neat.patch");
|
|
6727
7051
|
if (opts.dryRun) {
|
|
6728
|
-
await
|
|
7052
|
+
await import_node_fs25.promises.writeFile(patchPath, patch, "utf8");
|
|
6729
7053
|
written.push(patchPath);
|
|
6730
7054
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
6731
7055
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -6734,7 +7058,12 @@ async function runInit(opts) {
|
|
|
6734
7058
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
6735
7059
|
resetGraph(graphKey);
|
|
6736
7060
|
const graph = getGraph(graphKey);
|
|
6737
|
-
const
|
|
7061
|
+
const projectPaths = pathsForProject(
|
|
7062
|
+
graphKey,
|
|
7063
|
+
import_node_path40.default.join(opts.scanPath, "neat-out")
|
|
7064
|
+
);
|
|
7065
|
+
const errorsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), import_node_path40.default.basename(projectPaths.errorsPath));
|
|
7066
|
+
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
6738
7067
|
await saveGraphToDisk(graph, opts.outPath);
|
|
6739
7068
|
written.push(opts.outPath);
|
|
6740
7069
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
@@ -6765,7 +7094,7 @@ async function runInit(opts) {
|
|
|
6765
7094
|
console.log("patch applied. Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
6766
7095
|
}
|
|
6767
7096
|
} else {
|
|
6768
|
-
await
|
|
7097
|
+
await import_node_fs25.promises.writeFile(patchPath, patch, "utf8");
|
|
6769
7098
|
written.push(patchPath);
|
|
6770
7099
|
}
|
|
6771
7100
|
}
|
|
@@ -6779,6 +7108,11 @@ async function runInit(opts) {
|
|
|
6779
7108
|
console.log(`added: ${result.nodesAdded} nodes, ${result.edgesAdded} edges`);
|
|
6780
7109
|
console.log(`total: ${graph.order} nodes, ${graph.size} edges`);
|
|
6781
7110
|
console.log(summarise(nodes, edges));
|
|
7111
|
+
console.log(formatExtractionBanner(result.extractionErrors));
|
|
7112
|
+
if (result.extractionErrors > 0) {
|
|
7113
|
+
console.log(`errors: ${errorsPath}`);
|
|
7114
|
+
}
|
|
7115
|
+
console.log(formatPrecisionFloorBanner(result.extractedDropped));
|
|
6782
7116
|
const incompatibilities = findIncompatibilities(nodes);
|
|
6783
7117
|
if (incompatibilities.length > 0) {
|
|
6784
7118
|
console.log("");
|
|
@@ -6789,6 +7123,9 @@ async function runInit(opts) {
|
|
|
6789
7123
|
}
|
|
6790
7124
|
}
|
|
6791
7125
|
}
|
|
7126
|
+
if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
|
|
7127
|
+
return { exitCode: 4, writtenFiles: written };
|
|
7128
|
+
}
|
|
6792
7129
|
return { exitCode: 0, writtenFiles: written };
|
|
6793
7130
|
}
|
|
6794
7131
|
var CLAUDE_SKILL_CONFIG = {
|
|
@@ -6805,9 +7142,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
6805
7142
|
};
|
|
6806
7143
|
function claudeConfigPath() {
|
|
6807
7144
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
6808
|
-
if (override && override.length > 0) return
|
|
7145
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
6809
7146
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6810
|
-
return
|
|
7147
|
+
return import_node_path40.default.join(home, ".claude.json");
|
|
6811
7148
|
}
|
|
6812
7149
|
async function runSkill(opts) {
|
|
6813
7150
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -6819,7 +7156,7 @@ async function runSkill(opts) {
|
|
|
6819
7156
|
const target = claudeConfigPath();
|
|
6820
7157
|
let existing = {};
|
|
6821
7158
|
try {
|
|
6822
|
-
existing = JSON.parse(await
|
|
7159
|
+
existing = JSON.parse(await import_node_fs25.promises.readFile(target, "utf8"));
|
|
6823
7160
|
} catch (err) {
|
|
6824
7161
|
if (err.code !== "ENOENT") {
|
|
6825
7162
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -6831,8 +7168,8 @@ async function runSkill(opts) {
|
|
|
6831
7168
|
...existing,
|
|
6832
7169
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
6833
7170
|
};
|
|
6834
|
-
await
|
|
6835
|
-
await
|
|
7171
|
+
await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
|
|
7172
|
+
await import_node_fs25.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
6836
7173
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
6837
7174
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
6838
7175
|
return { exitCode: 0 };
|
|
@@ -6866,12 +7203,12 @@ async function main() {
|
|
|
6866
7203
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
6867
7204
|
process.exit(2);
|
|
6868
7205
|
}
|
|
6869
|
-
const scanPath =
|
|
7206
|
+
const scanPath = import_node_path40.default.resolve(target);
|
|
6870
7207
|
const projectExplicit = parsed.project !== null;
|
|
6871
|
-
const projectName = projectExplicit ? project :
|
|
7208
|
+
const projectName = projectExplicit ? project : import_node_path40.default.basename(scanPath);
|
|
6872
7209
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
6873
|
-
const fallback = pathsForProject(projectKey,
|
|
6874
|
-
const outPath =
|
|
7210
|
+
const fallback = pathsForProject(projectKey, import_node_path40.default.join(scanPath, "neat-out")).snapshotPath;
|
|
7211
|
+
const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
6875
7212
|
const result = await runInit({
|
|
6876
7213
|
scanPath,
|
|
6877
7214
|
outPath,
|
|
@@ -6891,21 +7228,21 @@ async function main() {
|
|
|
6891
7228
|
usage();
|
|
6892
7229
|
process.exit(2);
|
|
6893
7230
|
}
|
|
6894
|
-
const scanPath =
|
|
6895
|
-
const stat = await
|
|
7231
|
+
const scanPath = import_node_path40.default.resolve(target);
|
|
7232
|
+
const stat = await import_node_fs25.promises.stat(scanPath).catch(() => null);
|
|
6896
7233
|
if (!stat || !stat.isDirectory()) {
|
|
6897
7234
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
6898
7235
|
process.exit(2);
|
|
6899
7236
|
}
|
|
6900
|
-
const projectPaths = pathsForProject(project,
|
|
6901
|
-
const outPath =
|
|
6902
|
-
const errorsPath =
|
|
6903
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
7237
|
+
const projectPaths = pathsForProject(project, import_node_path40.default.join(scanPath, "neat-out"));
|
|
7238
|
+
const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
7239
|
+
const errorsPath = import_node_path40.default.resolve(
|
|
7240
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path40.default.join(import_node_path40.default.dirname(outPath), import_node_path40.default.basename(projectPaths.errorsPath))
|
|
6904
7241
|
);
|
|
6905
|
-
const staleEventsPath =
|
|
6906
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
7242
|
+
const staleEventsPath = import_node_path40.default.resolve(
|
|
7243
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path40.default.join(import_node_path40.default.dirname(outPath), import_node_path40.default.basename(projectPaths.staleEventsPath))
|
|
6907
7244
|
);
|
|
6908
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
7245
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path40.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
6909
7246
|
const handle = await startWatch(getGraph(project), {
|
|
6910
7247
|
scanPath,
|
|
6911
7248
|
outPath,
|