@neat.is/core 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T2U4U256.js → chunk-5KX7EI4F.js} +76 -13
- package/dist/chunk-5KX7EI4F.js.map +1 -0
- package/dist/{chunk-6JT6L2OV.js → chunk-BVF7MVR5.js} +8 -10
- package/dist/{chunk-6JT6L2OV.js.map → chunk-BVF7MVR5.js.map} +1 -1
- package/dist/{chunk-6SFEITLJ.js → chunk-IRPH6KL4.js} +541 -136
- package/dist/chunk-IRPH6KL4.js.map +1 -0
- package/dist/cli.cjs +1480 -479
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +22 -1
- package/dist/cli.d.ts +22 -1
- package/dist/cli.js +757 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +566 -339
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +16 -18
- package/dist/neatd.cjs +303 -153
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -3
- package/dist/neatd.js.map +1 -1
- package/dist/server.cjs +430 -168
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-6SFEITLJ.js.map +0 -1
- package/dist/chunk-T2U4U256.js.map +0 -1
- package/dist/chunk-WX55TLUT.js +0 -184
- package/dist/chunk-WX55TLUT.js.map +0 -1
package/dist/server.cjs
CHANGED
|
@@ -99,8 +99,8 @@ function reshapeGrpcRequest(req) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
function resolveProtoRoot() {
|
|
102
|
-
const here =
|
|
103
|
-
return
|
|
102
|
+
const here = import_node_path31.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
103
|
+
return import_node_path31.default.resolve(here, "..", "proto");
|
|
104
104
|
}
|
|
105
105
|
function loadTraceService() {
|
|
106
106
|
const protoRoot = resolveProtoRoot();
|
|
@@ -155,13 +155,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
155
155
|
})
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
-
var import_node_url,
|
|
158
|
+
var import_node_url, import_node_path31, grpc, protoLoader;
|
|
159
159
|
var init_otel_grpc = __esm({
|
|
160
160
|
"src/otel-grpc.ts"() {
|
|
161
161
|
"use strict";
|
|
162
162
|
init_cjs_shims();
|
|
163
163
|
import_node_url = require("url");
|
|
164
|
-
|
|
164
|
+
import_node_path31 = __toESM(require("path"), 1);
|
|
165
165
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
166
166
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
167
167
|
init_otel();
|
|
@@ -258,10 +258,10 @@ function parseOtlpRequest(body) {
|
|
|
258
258
|
}
|
|
259
259
|
function loadProtobufDecoder() {
|
|
260
260
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
261
|
-
const here =
|
|
262
|
-
const protoRoot =
|
|
261
|
+
const here = import_node_path32.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
262
|
+
const protoRoot = import_node_path32.default.resolve(here, "..", "proto");
|
|
263
263
|
const root = new import_protobufjs.default.Root();
|
|
264
|
-
root.resolvePath = (_origin, target) =>
|
|
264
|
+
root.resolvePath = (_origin, target) => import_node_path32.default.resolve(protoRoot, target);
|
|
265
265
|
root.loadSync(
|
|
266
266
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
267
267
|
{ keepCase: true }
|
|
@@ -353,12 +353,12 @@ async function buildOtelReceiver(opts) {
|
|
|
353
353
|
};
|
|
354
354
|
return decorated;
|
|
355
355
|
}
|
|
356
|
-
var
|
|
356
|
+
var import_node_path32, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
357
357
|
var init_otel = __esm({
|
|
358
358
|
"src/otel.ts"() {
|
|
359
359
|
"use strict";
|
|
360
360
|
init_cjs_shims();
|
|
361
|
-
|
|
361
|
+
import_node_path32 = __toESM(require("path"), 1);
|
|
362
362
|
import_node_url2 = require("url");
|
|
363
363
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
364
364
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -368,7 +368,7 @@ var init_otel = __esm({
|
|
|
368
368
|
|
|
369
369
|
// src/server.ts
|
|
370
370
|
init_cjs_shims();
|
|
371
|
-
var
|
|
371
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
372
372
|
|
|
373
373
|
// src/graph.ts
|
|
374
374
|
init_cjs_shims();
|
|
@@ -392,7 +392,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
392
392
|
init_cjs_shims();
|
|
393
393
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
394
394
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
395
|
-
var
|
|
395
|
+
var import_types19 = require("@neat.is/types");
|
|
396
396
|
|
|
397
397
|
// src/policy.ts
|
|
398
398
|
init_cjs_shims();
|
|
@@ -713,6 +713,18 @@ function deprecatedApis() {
|
|
|
713
713
|
return currentMatrix().deprecatedApis ?? [];
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
// src/events.ts
|
|
717
|
+
init_cjs_shims();
|
|
718
|
+
var import_node_events = require("events");
|
|
719
|
+
var EVENT_BUS_CHANNEL = "event";
|
|
720
|
+
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
721
|
+
};
|
|
722
|
+
var eventBus = new NeatEventBus();
|
|
723
|
+
eventBus.setMaxListeners(0);
|
|
724
|
+
function emitNeatEvent(envelope) {
|
|
725
|
+
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
726
|
+
}
|
|
727
|
+
|
|
716
728
|
// src/traverse.ts
|
|
717
729
|
init_cjs_shims();
|
|
718
730
|
var import_types = require("@neat.is/types");
|
|
@@ -800,19 +812,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
800
812
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
801
813
|
let best = { path: [start], edges: [] };
|
|
802
814
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
803
|
-
function step(node,
|
|
804
|
-
if (
|
|
805
|
-
best = { path: [...
|
|
815
|
+
function step(node, path35, edges) {
|
|
816
|
+
if (path35.length > best.path.length) {
|
|
817
|
+
best = { path: [...path35], edges: [...edges] };
|
|
806
818
|
}
|
|
807
|
-
if (
|
|
819
|
+
if (path35.length - 1 >= maxDepth) return;
|
|
808
820
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
809
821
|
for (const [srcId, edge] of incoming) {
|
|
810
822
|
if (visited.has(srcId)) continue;
|
|
811
823
|
visited.add(srcId);
|
|
812
|
-
|
|
824
|
+
path35.push(srcId);
|
|
813
825
|
edges.push(edge);
|
|
814
|
-
step(srcId,
|
|
815
|
-
|
|
826
|
+
step(srcId, path35, edges);
|
|
827
|
+
path35.pop();
|
|
816
828
|
edges.pop();
|
|
817
829
|
visited.delete(srcId);
|
|
818
830
|
}
|
|
@@ -1276,9 +1288,11 @@ async function loadPolicyFile(policyPath) {
|
|
|
1276
1288
|
}
|
|
1277
1289
|
var PolicyViolationsLog = class {
|
|
1278
1290
|
path;
|
|
1291
|
+
project;
|
|
1279
1292
|
seen = null;
|
|
1280
|
-
constructor(logPath) {
|
|
1293
|
+
constructor(logPath, project = DEFAULT_PROJECT) {
|
|
1281
1294
|
this.path = logPath;
|
|
1295
|
+
this.project = project;
|
|
1282
1296
|
}
|
|
1283
1297
|
async append(v) {
|
|
1284
1298
|
if (!this.seen) await this.hydrate();
|
|
@@ -1286,6 +1300,11 @@ var PolicyViolationsLog = class {
|
|
|
1286
1300
|
this.seen.add(v.id);
|
|
1287
1301
|
await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(this.path), { recursive: true });
|
|
1288
1302
|
await import_node_fs2.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
|
|
1303
|
+
emitNeatEvent({
|
|
1304
|
+
type: "policy-violation",
|
|
1305
|
+
project: this.project,
|
|
1306
|
+
payload: { violation: v }
|
|
1307
|
+
});
|
|
1289
1308
|
return true;
|
|
1290
1309
|
}
|
|
1291
1310
|
async readAll() {
|
|
@@ -1741,6 +1760,7 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1741
1760
|
const thresholds = options.thresholds ?? loadStaleThresholdsFromEnv();
|
|
1742
1761
|
const now = options.now ?? Date.now();
|
|
1743
1762
|
const events = [];
|
|
1763
|
+
const project = options.project ?? DEFAULT_PROJECT;
|
|
1744
1764
|
graph.forEachEdge((id, attrs) => {
|
|
1745
1765
|
const e = attrs;
|
|
1746
1766
|
if (e.provenance !== import_types3.Provenance.OBSERVED) return;
|
|
@@ -1760,6 +1780,15 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1760
1780
|
lastObserved: e.lastObserved,
|
|
1761
1781
|
transitionedAt: new Date(now).toISOString()
|
|
1762
1782
|
});
|
|
1783
|
+
emitNeatEvent({
|
|
1784
|
+
type: "stale-transition",
|
|
1785
|
+
project,
|
|
1786
|
+
payload: {
|
|
1787
|
+
edgeId: id,
|
|
1788
|
+
from: import_types3.Provenance.OBSERVED,
|
|
1789
|
+
to: import_types3.Provenance.STALE
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1763
1792
|
}
|
|
1764
1793
|
});
|
|
1765
1794
|
if (options.staleEventsPath && events.length > 0) {
|
|
@@ -1790,7 +1819,8 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
1790
1819
|
try {
|
|
1791
1820
|
await markStaleEdges(graph, {
|
|
1792
1821
|
thresholds: options.thresholds,
|
|
1793
|
-
staleEventsPath: options.staleEventsPath
|
|
1822
|
+
staleEventsPath: options.staleEventsPath,
|
|
1823
|
+
project: options.project
|
|
1794
1824
|
});
|
|
1795
1825
|
if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
|
|
1796
1826
|
} catch (err) {
|
|
@@ -1817,10 +1847,10 @@ async function readErrorEvents(errorsPath) {
|
|
|
1817
1847
|
|
|
1818
1848
|
// src/extract/services.ts
|
|
1819
1849
|
init_cjs_shims();
|
|
1820
|
-
var
|
|
1821
|
-
var
|
|
1850
|
+
var import_node_fs7 = require("fs");
|
|
1851
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1822
1852
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1823
|
-
var
|
|
1853
|
+
var import_minimatch2 = require("minimatch");
|
|
1824
1854
|
var import_types5 = require("@neat.is/types");
|
|
1825
1855
|
|
|
1826
1856
|
// src/extract/shared.ts
|
|
@@ -1933,6 +1963,73 @@ function pythonToPackage(service) {
|
|
|
1933
1963
|
};
|
|
1934
1964
|
}
|
|
1935
1965
|
|
|
1966
|
+
// src/extract/owners.ts
|
|
1967
|
+
init_cjs_shims();
|
|
1968
|
+
var import_node_fs6 = require("fs");
|
|
1969
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1970
|
+
var import_minimatch = require("minimatch");
|
|
1971
|
+
async function loadCodeowners(scanPath) {
|
|
1972
|
+
const candidates = [
|
|
1973
|
+
import_node_path6.default.join(scanPath, "CODEOWNERS"),
|
|
1974
|
+
import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
|
|
1975
|
+
];
|
|
1976
|
+
for (const file of candidates) {
|
|
1977
|
+
if (await exists(file)) {
|
|
1978
|
+
const raw = await import_node_fs6.promises.readFile(file, "utf8");
|
|
1979
|
+
return parseCodeowners(raw);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
function parseCodeowners(raw) {
|
|
1985
|
+
const rules = [];
|
|
1986
|
+
for (const line of raw.split("\n")) {
|
|
1987
|
+
const trimmed = line.trim();
|
|
1988
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1989
|
+
const match = /^(\S+)\s+(.+)$/.exec(trimmed);
|
|
1990
|
+
if (!match) continue;
|
|
1991
|
+
rules.push({ pattern: match[1], owners: match[2].trim() });
|
|
1992
|
+
}
|
|
1993
|
+
return { rules };
|
|
1994
|
+
}
|
|
1995
|
+
function matchOwner(file, repoPath) {
|
|
1996
|
+
const normalized = repoPath.split(import_node_path6.default.sep).join("/");
|
|
1997
|
+
for (const rule of file.rules) {
|
|
1998
|
+
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
1999
|
+
}
|
|
2000
|
+
return null;
|
|
2001
|
+
}
|
|
2002
|
+
function matchesPattern(rawPattern, repoPath) {
|
|
2003
|
+
let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
|
|
2004
|
+
if (pattern === "*") return !repoPath.includes("/");
|
|
2005
|
+
if (pattern === "**" || pattern === "") return true;
|
|
2006
|
+
if (pattern.endsWith("/")) pattern = pattern + "**";
|
|
2007
|
+
if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
|
|
2008
|
+
if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
async function readPackageJsonAuthor(serviceDir) {
|
|
2012
|
+
const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
|
|
2013
|
+
if (!await exists(pkgPath)) return null;
|
|
2014
|
+
try {
|
|
2015
|
+
const pkg = await readJson(pkgPath);
|
|
2016
|
+
if (!pkg.author) return null;
|
|
2017
|
+
if (typeof pkg.author === "string") return pkg.author;
|
|
2018
|
+
if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
|
|
2019
|
+
return null;
|
|
2020
|
+
} catch {
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
2025
|
+
if (codeowners && repoPath !== void 0) {
|
|
2026
|
+
const owner = matchOwner(codeowners, repoPath);
|
|
2027
|
+
if (owner) return owner;
|
|
2028
|
+
}
|
|
2029
|
+
const author = await readPackageJsonAuthor(serviceDir);
|
|
2030
|
+
return author ?? void 0;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1936
2033
|
// src/extract/services.ts
|
|
1937
2034
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
1938
2035
|
function parseScanDepth() {
|
|
@@ -1949,21 +2046,21 @@ function workspaceGlobs(pkg) {
|
|
|
1949
2046
|
return null;
|
|
1950
2047
|
}
|
|
1951
2048
|
async function loadGitignore(scanPath) {
|
|
1952
|
-
const gitignorePath =
|
|
2049
|
+
const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
|
|
1953
2050
|
if (!await exists(gitignorePath)) return null;
|
|
1954
|
-
const raw = await
|
|
2051
|
+
const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
|
|
1955
2052
|
return (0, import_ignore.default)().add(raw);
|
|
1956
2053
|
}
|
|
1957
2054
|
async function walkDirs(start, scanPath, options, visit) {
|
|
1958
2055
|
async function recurse(current, depth) {
|
|
1959
2056
|
if (depth > options.maxDepth) return;
|
|
1960
|
-
const entries = await
|
|
2057
|
+
const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1961
2058
|
for (const entry of entries) {
|
|
1962
2059
|
if (!entry.isDirectory()) continue;
|
|
1963
2060
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1964
|
-
const child =
|
|
2061
|
+
const child = import_node_path7.default.join(current, entry.name);
|
|
1965
2062
|
if (options.ig) {
|
|
1966
|
-
const rel =
|
|
2063
|
+
const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
|
|
1967
2064
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
1968
2065
|
}
|
|
1969
2066
|
await visit(child);
|
|
@@ -1978,8 +2075,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1978
2075
|
for (const raw of globs) {
|
|
1979
2076
|
const pattern = raw.replace(/^\.\//, "");
|
|
1980
2077
|
if (!pattern.includes("*")) {
|
|
1981
|
-
const candidate =
|
|
1982
|
-
if (await exists(
|
|
2078
|
+
const candidate = import_node_path7.default.join(scanPath, pattern);
|
|
2079
|
+
if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
|
|
1983
2080
|
continue;
|
|
1984
2081
|
}
|
|
1985
2082
|
const segments = pattern.split("/");
|
|
@@ -1988,13 +2085,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1988
2085
|
if (seg.includes("*")) break;
|
|
1989
2086
|
staticSegments.push(seg);
|
|
1990
2087
|
}
|
|
1991
|
-
const start =
|
|
2088
|
+
const start = import_node_path7.default.join(scanPath, ...staticSegments);
|
|
1992
2089
|
if (!await exists(start)) continue;
|
|
1993
2090
|
const hasDoubleStar = pattern.includes("**");
|
|
1994
2091
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
1995
2092
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
1996
|
-
const rel =
|
|
1997
|
-
if ((0,
|
|
2093
|
+
const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
|
|
2094
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
1998
2095
|
found.add(dir);
|
|
1999
2096
|
}
|
|
2000
2097
|
});
|
|
@@ -2002,9 +2099,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2002
2099
|
return [...found];
|
|
2003
2100
|
}
|
|
2004
2101
|
async function discoverNodeService(scanPath, dir) {
|
|
2005
|
-
const pkgPath =
|
|
2102
|
+
const pkgPath = import_node_path7.default.join(dir, "package.json");
|
|
2006
2103
|
if (!await exists(pkgPath)) return null;
|
|
2007
|
-
|
|
2104
|
+
let pkg;
|
|
2105
|
+
try {
|
|
2106
|
+
pkg = await readJson(pkgPath);
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
console.warn(
|
|
2109
|
+
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2110
|
+
);
|
|
2111
|
+
return null;
|
|
2112
|
+
}
|
|
2008
2113
|
if (!pkg.name) return null;
|
|
2009
2114
|
const node = {
|
|
2010
2115
|
id: (0, import_types5.serviceId)(pkg.name),
|
|
@@ -2013,7 +2118,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2013
2118
|
language: "javascript",
|
|
2014
2119
|
version: pkg.version,
|
|
2015
2120
|
dependencies: pkg.dependencies ?? {},
|
|
2016
|
-
repoPath:
|
|
2121
|
+
repoPath: import_node_path7.default.relative(scanPath, dir),
|
|
2017
2122
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2018
2123
|
};
|
|
2019
2124
|
return { pkg, dir, node };
|
|
@@ -2029,13 +2134,22 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2029
2134
|
language: "python",
|
|
2030
2135
|
version: py.version,
|
|
2031
2136
|
dependencies: py.dependencies,
|
|
2032
|
-
repoPath:
|
|
2137
|
+
repoPath: import_node_path7.default.relative(scanPath, dir)
|
|
2033
2138
|
};
|
|
2034
2139
|
return { pkg, dir, node };
|
|
2035
2140
|
}
|
|
2036
2141
|
async function discoverServices(scanPath) {
|
|
2037
|
-
const rootPkgPath =
|
|
2038
|
-
|
|
2142
|
+
const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
|
|
2143
|
+
let rootPkg = null;
|
|
2144
|
+
if (await exists(rootPkgPath)) {
|
|
2145
|
+
try {
|
|
2146
|
+
rootPkg = await readJson(rootPkgPath);
|
|
2147
|
+
} catch (err) {
|
|
2148
|
+
console.warn(
|
|
2149
|
+
`[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2039
2153
|
const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
|
|
2040
2154
|
const candidateDirs = [];
|
|
2041
2155
|
if (wsGlobs) {
|
|
@@ -2048,9 +2162,9 @@ async function discoverServices(scanPath) {
|
|
|
2048
2162
|
scanPath,
|
|
2049
2163
|
{ maxDepth: parseScanDepth(), ig },
|
|
2050
2164
|
async (dir) => {
|
|
2051
|
-
if (await exists(
|
|
2165
|
+
if (await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
2052
2166
|
candidateDirs.push(dir);
|
|
2053
|
-
} else if (await exists(
|
|
2167
|
+
} else if (await exists(import_node_path7.default.join(dir, "pyproject.toml")) || await exists(import_node_path7.default.join(dir, "requirements.txt")) || await exists(import_node_path7.default.join(dir, "setup.py"))) {
|
|
2054
2168
|
candidateDirs.push(dir);
|
|
2055
2169
|
}
|
|
2056
2170
|
}
|
|
@@ -2064,8 +2178,8 @@ async function discoverServices(scanPath) {
|
|
|
2064
2178
|
if (!service) continue;
|
|
2065
2179
|
const existingDir = seen.get(service.node.name);
|
|
2066
2180
|
if (existingDir !== void 0) {
|
|
2067
|
-
const a =
|
|
2068
|
-
const b =
|
|
2181
|
+
const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
|
|
2182
|
+
const b = import_node_path7.default.relative(scanPath, dir) || ".";
|
|
2069
2183
|
console.warn(
|
|
2070
2184
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2071
2185
|
);
|
|
@@ -2074,6 +2188,11 @@ async function discoverServices(scanPath) {
|
|
|
2074
2188
|
seen.set(service.node.name, dir);
|
|
2075
2189
|
out.push(service);
|
|
2076
2190
|
}
|
|
2191
|
+
const codeowners = await loadCodeowners(scanPath);
|
|
2192
|
+
for (const service of out) {
|
|
2193
|
+
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
2194
|
+
if (owner !== void 0) service.node.owner = owner;
|
|
2195
|
+
}
|
|
2077
2196
|
return out;
|
|
2078
2197
|
}
|
|
2079
2198
|
function addServiceNodes(graph, services) {
|
|
@@ -2097,8 +2216,8 @@ function addServiceNodes(graph, services) {
|
|
|
2097
2216
|
|
|
2098
2217
|
// src/extract/aliases.ts
|
|
2099
2218
|
init_cjs_shims();
|
|
2100
|
-
var
|
|
2101
|
-
var
|
|
2219
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2220
|
+
var import_node_fs8 = require("fs");
|
|
2102
2221
|
var import_yaml2 = require("yaml");
|
|
2103
2222
|
var import_types6 = require("@neat.is/types");
|
|
2104
2223
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2125,21 +2244,29 @@ function indexServicesByName(services) {
|
|
|
2125
2244
|
const map = /* @__PURE__ */ new Map();
|
|
2126
2245
|
for (const s of services) {
|
|
2127
2246
|
map.set(s.node.name, s.node.id);
|
|
2128
|
-
map.set(
|
|
2247
|
+
map.set(import_node_path8.default.basename(s.dir), s.node.id);
|
|
2129
2248
|
}
|
|
2130
2249
|
return map;
|
|
2131
2250
|
}
|
|
2132
2251
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2133
2252
|
let composePath = null;
|
|
2134
2253
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2135
|
-
const abs =
|
|
2254
|
+
const abs = import_node_path8.default.join(scanPath, name);
|
|
2136
2255
|
if (await exists(abs)) {
|
|
2137
2256
|
composePath = abs;
|
|
2138
2257
|
break;
|
|
2139
2258
|
}
|
|
2140
2259
|
}
|
|
2141
2260
|
if (!composePath) return;
|
|
2142
|
-
|
|
2261
|
+
let compose;
|
|
2262
|
+
try {
|
|
2263
|
+
compose = await readYaml(composePath);
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
console.warn(
|
|
2266
|
+
`[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
|
|
2267
|
+
);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2143
2270
|
if (!compose?.services) return;
|
|
2144
2271
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
2145
2272
|
const serviceId3 = serviceIndex.get(composeName);
|
|
@@ -2178,9 +2305,17 @@ function parseDockerfileLabels(content) {
|
|
|
2178
2305
|
}
|
|
2179
2306
|
async function collectDockerfileAliases(graph, services) {
|
|
2180
2307
|
for (const service of services) {
|
|
2181
|
-
const dockerfilePath =
|
|
2308
|
+
const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
|
|
2182
2309
|
if (!await exists(dockerfilePath)) continue;
|
|
2183
|
-
|
|
2310
|
+
let content;
|
|
2311
|
+
try {
|
|
2312
|
+
content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
console.warn(
|
|
2315
|
+
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2316
|
+
);
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2184
2319
|
const aliases = parseDockerfileLabels(content);
|
|
2185
2320
|
if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
|
|
2186
2321
|
}
|
|
@@ -2188,13 +2323,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2188
2323
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2189
2324
|
if (depth > max) return [];
|
|
2190
2325
|
const out = [];
|
|
2191
|
-
const entries = await
|
|
2326
|
+
const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2192
2327
|
for (const entry of entries) {
|
|
2193
2328
|
if (entry.isDirectory()) {
|
|
2194
2329
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2195
|
-
out.push(...await walkYamlFiles(
|
|
2196
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2197
|
-
out.push(
|
|
2330
|
+
out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry.name), depth + 1, max));
|
|
2331
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry.name))) {
|
|
2332
|
+
out.push(import_node_path8.default.join(start, entry.name));
|
|
2198
2333
|
}
|
|
2199
2334
|
}
|
|
2200
2335
|
return out;
|
|
@@ -2221,7 +2356,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2221
2356
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2222
2357
|
const files = await walkYamlFiles(scanPath);
|
|
2223
2358
|
for (const file of files) {
|
|
2224
|
-
const content = await
|
|
2359
|
+
const content = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2225
2360
|
let docs;
|
|
2226
2361
|
try {
|
|
2227
2362
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2246,14 +2381,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2246
2381
|
|
|
2247
2382
|
// src/extract/databases/index.ts
|
|
2248
2383
|
init_cjs_shims();
|
|
2249
|
-
var
|
|
2384
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2250
2385
|
var import_types7 = require("@neat.is/types");
|
|
2251
2386
|
|
|
2252
2387
|
// src/extract/databases/db-config-yaml.ts
|
|
2253
2388
|
init_cjs_shims();
|
|
2254
|
-
var
|
|
2389
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2255
2390
|
async function parse(serviceDir) {
|
|
2256
|
-
const yamlPath =
|
|
2391
|
+
const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
|
|
2257
2392
|
if (!await exists(yamlPath)) return [];
|
|
2258
2393
|
const raw = await readYaml(yamlPath);
|
|
2259
2394
|
return [
|
|
@@ -2271,13 +2406,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2271
2406
|
|
|
2272
2407
|
// src/extract/databases/dotenv.ts
|
|
2273
2408
|
init_cjs_shims();
|
|
2274
|
-
var
|
|
2275
|
-
var
|
|
2409
|
+
var import_node_fs10 = require("fs");
|
|
2410
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2276
2411
|
|
|
2277
2412
|
// src/extract/databases/shared.ts
|
|
2278
2413
|
init_cjs_shims();
|
|
2279
|
-
var
|
|
2280
|
-
var
|
|
2414
|
+
var import_node_fs9 = require("fs");
|
|
2415
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2281
2416
|
function schemeToEngine(scheme) {
|
|
2282
2417
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2283
2418
|
switch (s) {
|
|
@@ -2316,14 +2451,14 @@ function parseConnectionString(url) {
|
|
|
2316
2451
|
}
|
|
2317
2452
|
async function readIfExists(filePath) {
|
|
2318
2453
|
try {
|
|
2319
|
-
return await
|
|
2454
|
+
return await import_node_fs9.promises.readFile(filePath, "utf8");
|
|
2320
2455
|
} catch {
|
|
2321
2456
|
return null;
|
|
2322
2457
|
}
|
|
2323
2458
|
}
|
|
2324
2459
|
async function findFirst(serviceDir, candidates) {
|
|
2325
2460
|
for (const rel of candidates) {
|
|
2326
|
-
const abs =
|
|
2461
|
+
const abs = import_node_path10.default.join(serviceDir, rel);
|
|
2327
2462
|
const content = await readIfExists(abs);
|
|
2328
2463
|
if (content !== null) return abs;
|
|
2329
2464
|
}
|
|
@@ -2374,15 +2509,15 @@ function parseDotenvLine(line) {
|
|
|
2374
2509
|
return { key, value };
|
|
2375
2510
|
}
|
|
2376
2511
|
async function parse2(serviceDir) {
|
|
2377
|
-
const entries = await
|
|
2512
|
+
const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2378
2513
|
const configs = [];
|
|
2379
2514
|
const seen = /* @__PURE__ */ new Set();
|
|
2380
2515
|
for (const entry of entries) {
|
|
2381
2516
|
if (!entry.isFile()) continue;
|
|
2382
2517
|
const match = isConfigFile(entry.name);
|
|
2383
2518
|
if (!match.match || match.fileType !== "env") continue;
|
|
2384
|
-
const filePath =
|
|
2385
|
-
const content = await
|
|
2519
|
+
const filePath = import_node_path11.default.join(serviceDir, entry.name);
|
|
2520
|
+
const content = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2386
2521
|
for (const line of content.split("\n")) {
|
|
2387
2522
|
const parsed = parseDotenvLine(line);
|
|
2388
2523
|
if (!parsed) continue;
|
|
@@ -2401,9 +2536,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2401
2536
|
|
|
2402
2537
|
// src/extract/databases/prisma.ts
|
|
2403
2538
|
init_cjs_shims();
|
|
2404
|
-
var
|
|
2539
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2405
2540
|
async function parse3(serviceDir) {
|
|
2406
|
-
const schemaPath =
|
|
2541
|
+
const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2407
2542
|
const content = await readIfExists(schemaPath);
|
|
2408
2543
|
if (!content) return [];
|
|
2409
2544
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2535,10 +2670,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2535
2670
|
|
|
2536
2671
|
// src/extract/databases/ormconfig.ts
|
|
2537
2672
|
init_cjs_shims();
|
|
2538
|
-
var
|
|
2673
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2539
2674
|
async function parse6(serviceDir) {
|
|
2540
2675
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2541
|
-
const abs =
|
|
2676
|
+
const abs = import_node_path13.default.join(serviceDir, candidate);
|
|
2542
2677
|
if (!await exists(abs)) continue;
|
|
2543
2678
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2544
2679
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2598,9 +2733,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2598
2733
|
|
|
2599
2734
|
// src/extract/databases/sequelize.ts
|
|
2600
2735
|
init_cjs_shims();
|
|
2601
|
-
var
|
|
2736
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2602
2737
|
async function parse8(serviceDir) {
|
|
2603
|
-
const configPath =
|
|
2738
|
+
const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
|
|
2604
2739
|
if (!await exists(configPath)) return [];
|
|
2605
2740
|
const raw = await readJson(configPath);
|
|
2606
2741
|
const out = [];
|
|
@@ -2627,7 +2762,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2627
2762
|
|
|
2628
2763
|
// src/extract/databases/docker-compose.ts
|
|
2629
2764
|
init_cjs_shims();
|
|
2630
|
-
var
|
|
2765
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2631
2766
|
function portFromService(svc) {
|
|
2632
2767
|
for (const raw of svc.ports ?? []) {
|
|
2633
2768
|
const str = String(raw);
|
|
@@ -2654,7 +2789,7 @@ function databaseFromEnv(svc) {
|
|
|
2654
2789
|
}
|
|
2655
2790
|
async function parse9(serviceDir) {
|
|
2656
2791
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2657
|
-
const abs =
|
|
2792
|
+
const abs = import_node_path15.default.join(serviceDir, name);
|
|
2658
2793
|
if (!await exists(abs)) continue;
|
|
2659
2794
|
const raw = await readYaml(abs);
|
|
2660
2795
|
if (!raw?.services) return [];
|
|
@@ -2834,7 +2969,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2834
2969
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
2835
2970
|
...config.sourceFile ? {
|
|
2836
2971
|
evidence: {
|
|
2837
|
-
file:
|
|
2972
|
+
file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
|
|
2838
2973
|
}
|
|
2839
2974
|
} : {}
|
|
2840
2975
|
};
|
|
@@ -2862,15 +2997,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2862
2997
|
|
|
2863
2998
|
// src/extract/configs.ts
|
|
2864
2999
|
init_cjs_shims();
|
|
2865
|
-
var
|
|
2866
|
-
var
|
|
3000
|
+
var import_node_fs11 = require("fs");
|
|
3001
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2867
3002
|
var import_types8 = require("@neat.is/types");
|
|
2868
3003
|
async function walkConfigFiles(dir) {
|
|
2869
3004
|
const out = [];
|
|
2870
3005
|
async function walk(current) {
|
|
2871
|
-
const entries = await
|
|
3006
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
|
|
2872
3007
|
for (const entry of entries) {
|
|
2873
|
-
const full =
|
|
3008
|
+
const full = import_node_path17.default.join(current, entry.name);
|
|
2874
3009
|
if (entry.isDirectory()) {
|
|
2875
3010
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2876
3011
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -2887,13 +3022,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2887
3022
|
for (const service of services) {
|
|
2888
3023
|
const configFiles = await walkConfigFiles(service.dir);
|
|
2889
3024
|
for (const file of configFiles) {
|
|
2890
|
-
const relPath =
|
|
3025
|
+
const relPath = import_node_path17.default.relative(scanPath, file);
|
|
2891
3026
|
const node = {
|
|
2892
3027
|
id: (0, import_types8.configId)(relPath),
|
|
2893
3028
|
type: import_types8.NodeType.ConfigNode,
|
|
2894
|
-
name:
|
|
3029
|
+
name: import_node_path17.default.basename(file),
|
|
2895
3030
|
path: relPath,
|
|
2896
|
-
fileType: isConfigFile(
|
|
3031
|
+
fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
|
|
2897
3032
|
};
|
|
2898
3033
|
if (!graph.hasNode(node.id)) {
|
|
2899
3034
|
graph.addNode(node.id, node);
|
|
@@ -2905,7 +3040,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2905
3040
|
target: node.id,
|
|
2906
3041
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
2907
3042
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
2908
|
-
evidence: { file: relPath.split(
|
|
3043
|
+
evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
|
|
2909
3044
|
};
|
|
2910
3045
|
if (!graph.hasEdge(edge.id)) {
|
|
2911
3046
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2922,7 +3057,7 @@ var import_types14 = require("@neat.is/types");
|
|
|
2922
3057
|
|
|
2923
3058
|
// src/extract/calls/http.ts
|
|
2924
3059
|
init_cjs_shims();
|
|
2925
|
-
var
|
|
3060
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
2926
3061
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2927
3062
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2928
3063
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -2930,17 +3065,17 @@ var import_types9 = require("@neat.is/types");
|
|
|
2930
3065
|
|
|
2931
3066
|
// src/extract/calls/shared.ts
|
|
2932
3067
|
init_cjs_shims();
|
|
2933
|
-
var
|
|
2934
|
-
var
|
|
3068
|
+
var import_node_fs12 = require("fs");
|
|
3069
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2935
3070
|
async function walkSourceFiles(dir) {
|
|
2936
3071
|
const out = [];
|
|
2937
3072
|
async function walk(current) {
|
|
2938
|
-
const entries = await
|
|
3073
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2939
3074
|
for (const entry of entries) {
|
|
2940
|
-
const full =
|
|
3075
|
+
const full = import_node_path18.default.join(current, entry.name);
|
|
2941
3076
|
if (entry.isDirectory()) {
|
|
2942
3077
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2943
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3078
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry.name))) {
|
|
2944
3079
|
out.push(full);
|
|
2945
3080
|
}
|
|
2946
3081
|
}
|
|
@@ -2953,7 +3088,7 @@ async function loadSourceFiles(dir) {
|
|
|
2953
3088
|
const out = [];
|
|
2954
3089
|
for (const p of paths) {
|
|
2955
3090
|
try {
|
|
2956
|
-
const content = await
|
|
3091
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
2957
3092
|
out.push({ path: p, content });
|
|
2958
3093
|
} catch {
|
|
2959
3094
|
}
|
|
@@ -3009,9 +3144,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3009
3144
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3010
3145
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3011
3146
|
for (const service of services) {
|
|
3012
|
-
knownHosts.add(
|
|
3147
|
+
knownHosts.add(import_node_path19.default.basename(service.dir));
|
|
3013
3148
|
knownHosts.add(service.pkg.name);
|
|
3014
|
-
hostToNodeId.set(
|
|
3149
|
+
hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
|
|
3015
3150
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3016
3151
|
}
|
|
3017
3152
|
let edgesAdded = 0;
|
|
@@ -3019,8 +3154,16 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3019
3154
|
const files = await loadSourceFiles(service.dir);
|
|
3020
3155
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3021
3156
|
for (const file of files) {
|
|
3022
|
-
const parser =
|
|
3023
|
-
|
|
3157
|
+
const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3158
|
+
let targets;
|
|
3159
|
+
try {
|
|
3160
|
+
targets = callsFromSource(file.content, parser, knownHosts);
|
|
3161
|
+
} catch (err) {
|
|
3162
|
+
console.warn(
|
|
3163
|
+
`[neat] http call extraction skipped ${file.path}: ${err.message}`
|
|
3164
|
+
);
|
|
3165
|
+
continue;
|
|
3166
|
+
}
|
|
3024
3167
|
for (const t of targets) {
|
|
3025
3168
|
const targetId = hostToNodeId.get(t);
|
|
3026
3169
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -3039,7 +3182,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3039
3182
|
type: import_types9.EdgeType.CALLS,
|
|
3040
3183
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3041
3184
|
evidence: {
|
|
3042
|
-
file:
|
|
3185
|
+
file: import_node_path19.default.relative(service.dir, evidenceFile.file),
|
|
3043
3186
|
line,
|
|
3044
3187
|
snippet: snippet(fileContent, line)
|
|
3045
3188
|
}
|
|
@@ -3055,7 +3198,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3055
3198
|
|
|
3056
3199
|
// src/extract/calls/kafka.ts
|
|
3057
3200
|
init_cjs_shims();
|
|
3058
|
-
var
|
|
3201
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3059
3202
|
var import_types10 = require("@neat.is/types");
|
|
3060
3203
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3061
3204
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3082,7 +3225,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3082
3225
|
kind: "kafka-topic",
|
|
3083
3226
|
edgeType,
|
|
3084
3227
|
evidence: {
|
|
3085
|
-
file:
|
|
3228
|
+
file: import_node_path20.default.relative(serviceDir, file.path),
|
|
3086
3229
|
line,
|
|
3087
3230
|
snippet: snippet(file.content, line)
|
|
3088
3231
|
}
|
|
@@ -3095,7 +3238,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3095
3238
|
|
|
3096
3239
|
// src/extract/calls/redis.ts
|
|
3097
3240
|
init_cjs_shims();
|
|
3098
|
-
var
|
|
3241
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3099
3242
|
var import_types11 = require("@neat.is/types");
|
|
3100
3243
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3101
3244
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3114,7 +3257,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3114
3257
|
kind: "redis",
|
|
3115
3258
|
edgeType: "CALLS",
|
|
3116
3259
|
evidence: {
|
|
3117
|
-
file:
|
|
3260
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3118
3261
|
line,
|
|
3119
3262
|
snippet: snippet(file.content, line)
|
|
3120
3263
|
}
|
|
@@ -3125,7 +3268,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3125
3268
|
|
|
3126
3269
|
// src/extract/calls/aws.ts
|
|
3127
3270
|
init_cjs_shims();
|
|
3128
|
-
var
|
|
3271
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3129
3272
|
var import_types12 = require("@neat.is/types");
|
|
3130
3273
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3131
3274
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3155,7 +3298,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3155
3298
|
kind,
|
|
3156
3299
|
edgeType: "CALLS",
|
|
3157
3300
|
evidence: {
|
|
3158
|
-
file:
|
|
3301
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3159
3302
|
line,
|
|
3160
3303
|
snippet: snippet(file.content, line)
|
|
3161
3304
|
}
|
|
@@ -3180,7 +3323,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3180
3323
|
|
|
3181
3324
|
// src/extract/calls/grpc.ts
|
|
3182
3325
|
init_cjs_shims();
|
|
3183
|
-
var
|
|
3326
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3184
3327
|
var import_types13 = require("@neat.is/types");
|
|
3185
3328
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3186
3329
|
function isLikelyAddress(value) {
|
|
@@ -3205,7 +3348,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3205
3348
|
kind: "grpc-service",
|
|
3206
3349
|
edgeType: "CALLS",
|
|
3207
3350
|
evidence: {
|
|
3208
|
-
file:
|
|
3351
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3209
3352
|
line,
|
|
3210
3353
|
snippet: snippet(file.content, line)
|
|
3211
3354
|
}
|
|
@@ -3285,7 +3428,7 @@ init_cjs_shims();
|
|
|
3285
3428
|
|
|
3286
3429
|
// src/extract/infra/docker-compose.ts
|
|
3287
3430
|
init_cjs_shims();
|
|
3288
|
-
var
|
|
3431
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3289
3432
|
var import_types16 = require("@neat.is/types");
|
|
3290
3433
|
|
|
3291
3434
|
// src/extract/infra/shared.ts
|
|
@@ -3323,7 +3466,7 @@ function dependsOnList(value) {
|
|
|
3323
3466
|
}
|
|
3324
3467
|
function serviceNameToServiceNode(name, services) {
|
|
3325
3468
|
for (const s of services) {
|
|
3326
|
-
if (s.node.name === name ||
|
|
3469
|
+
if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
|
|
3327
3470
|
}
|
|
3328
3471
|
return null;
|
|
3329
3472
|
}
|
|
@@ -3332,16 +3475,24 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3332
3475
|
let edgesAdded = 0;
|
|
3333
3476
|
let composePath = null;
|
|
3334
3477
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3335
|
-
const abs =
|
|
3478
|
+
const abs = import_node_path24.default.join(scanPath, name);
|
|
3336
3479
|
if (await exists(abs)) {
|
|
3337
3480
|
composePath = abs;
|
|
3338
3481
|
break;
|
|
3339
3482
|
}
|
|
3340
3483
|
}
|
|
3341
3484
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
3342
|
-
|
|
3485
|
+
let compose;
|
|
3486
|
+
try {
|
|
3487
|
+
compose = await readYaml(composePath);
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
console.warn(
|
|
3490
|
+
`[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
|
|
3491
|
+
);
|
|
3492
|
+
return { nodesAdded, edgesAdded };
|
|
3493
|
+
}
|
|
3343
3494
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3344
|
-
const evidenceFile =
|
|
3495
|
+
const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
|
|
3345
3496
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3346
3497
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3347
3498
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3382,8 +3533,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3382
3533
|
|
|
3383
3534
|
// src/extract/infra/dockerfile.ts
|
|
3384
3535
|
init_cjs_shims();
|
|
3385
|
-
var
|
|
3386
|
-
var
|
|
3536
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3537
|
+
var import_node_fs13 = require("fs");
|
|
3387
3538
|
var import_types17 = require("@neat.is/types");
|
|
3388
3539
|
function runtimeImage(content) {
|
|
3389
3540
|
const lines = content.split("\n");
|
|
@@ -3403,9 +3554,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3403
3554
|
let nodesAdded = 0;
|
|
3404
3555
|
let edgesAdded = 0;
|
|
3405
3556
|
for (const service of services) {
|
|
3406
|
-
const dockerfilePath =
|
|
3557
|
+
const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
|
|
3407
3558
|
if (!await exists(dockerfilePath)) continue;
|
|
3408
|
-
|
|
3559
|
+
let content;
|
|
3560
|
+
try {
|
|
3561
|
+
content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
|
|
3562
|
+
} catch (err) {
|
|
3563
|
+
console.warn(
|
|
3564
|
+
`[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
|
|
3565
|
+
);
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3409
3568
|
const image = runtimeImage(content);
|
|
3410
3569
|
if (!image) continue;
|
|
3411
3570
|
const node = makeInfraNode("container-image", image);
|
|
@@ -3422,7 +3581,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3422
3581
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3423
3582
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3424
3583
|
evidence: {
|
|
3425
|
-
file:
|
|
3584
|
+
file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
|
|
3426
3585
|
}
|
|
3427
3586
|
};
|
|
3428
3587
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3434,19 +3593,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3434
3593
|
|
|
3435
3594
|
// src/extract/infra/terraform.ts
|
|
3436
3595
|
init_cjs_shims();
|
|
3437
|
-
var
|
|
3438
|
-
var
|
|
3596
|
+
var import_node_fs14 = require("fs");
|
|
3597
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3439
3598
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3440
3599
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3441
3600
|
if (depth > max) return [];
|
|
3442
3601
|
const out = [];
|
|
3443
|
-
const entries = await
|
|
3602
|
+
const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3444
3603
|
for (const entry of entries) {
|
|
3445
3604
|
if (entry.isDirectory()) {
|
|
3446
3605
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3447
|
-
out.push(...await walkTfFiles(
|
|
3606
|
+
out.push(...await walkTfFiles(import_node_path26.default.join(start, entry.name), depth + 1, max));
|
|
3448
3607
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3449
|
-
out.push(
|
|
3608
|
+
out.push(import_node_path26.default.join(start, entry.name));
|
|
3450
3609
|
}
|
|
3451
3610
|
}
|
|
3452
3611
|
return out;
|
|
@@ -3455,7 +3614,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3455
3614
|
let nodesAdded = 0;
|
|
3456
3615
|
const files = await walkTfFiles(scanPath);
|
|
3457
3616
|
for (const file of files) {
|
|
3458
|
-
const content = await
|
|
3617
|
+
const content = await import_node_fs14.promises.readFile(file, "utf8");
|
|
3459
3618
|
RESOURCE_RE.lastIndex = 0;
|
|
3460
3619
|
let m;
|
|
3461
3620
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3473,8 +3632,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3473
3632
|
|
|
3474
3633
|
// src/extract/infra/k8s.ts
|
|
3475
3634
|
init_cjs_shims();
|
|
3476
|
-
var
|
|
3477
|
-
var
|
|
3635
|
+
var import_node_fs15 = require("fs");
|
|
3636
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3478
3637
|
var import_yaml3 = require("yaml");
|
|
3479
3638
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3480
3639
|
Service: "k8s-service",
|
|
@@ -3488,13 +3647,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3488
3647
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3489
3648
|
if (depth > max) return [];
|
|
3490
3649
|
const out = [];
|
|
3491
|
-
const entries = await
|
|
3650
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3492
3651
|
for (const entry of entries) {
|
|
3493
3652
|
if (entry.isDirectory()) {
|
|
3494
3653
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3495
|
-
out.push(...await walkYamlFiles2(
|
|
3496
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3497
|
-
out.push(
|
|
3654
|
+
out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry.name), depth + 1, max));
|
|
3655
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry.name))) {
|
|
3656
|
+
out.push(import_node_path27.default.join(start, entry.name));
|
|
3498
3657
|
}
|
|
3499
3658
|
}
|
|
3500
3659
|
return out;
|
|
@@ -3503,7 +3662,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3503
3662
|
let nodesAdded = 0;
|
|
3504
3663
|
const files = await walkYamlFiles2(scanPath);
|
|
3505
3664
|
for (const file of files) {
|
|
3506
|
-
const content = await
|
|
3665
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3507
3666
|
let docs;
|
|
3508
3667
|
try {
|
|
3509
3668
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3549,16 +3708,27 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3549
3708
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
3550
3709
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
3551
3710
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
3552
|
-
|
|
3711
|
+
const result = {
|
|
3553
3712
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3554
3713
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3555
3714
|
frontiersPromoted
|
|
3556
3715
|
};
|
|
3716
|
+
emitNeatEvent({
|
|
3717
|
+
type: "extraction-complete",
|
|
3718
|
+
project: opts.project ?? DEFAULT_PROJECT,
|
|
3719
|
+
payload: {
|
|
3720
|
+
project: opts.project ?? DEFAULT_PROJECT,
|
|
3721
|
+
fileCount: services.length,
|
|
3722
|
+
nodesAdded: result.nodesAdded,
|
|
3723
|
+
edgesAdded: result.edgesAdded
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
return result;
|
|
3557
3727
|
}
|
|
3558
3728
|
|
|
3559
3729
|
// src/diff.ts
|
|
3560
3730
|
init_cjs_shims();
|
|
3561
|
-
var
|
|
3731
|
+
var import_node_fs16 = require("fs");
|
|
3562
3732
|
async function loadSnapshotForDiff(target) {
|
|
3563
3733
|
if (/^https?:\/\//i.test(target)) {
|
|
3564
3734
|
const res = await fetch(target);
|
|
@@ -3567,7 +3737,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
3567
3737
|
}
|
|
3568
3738
|
return await res.json();
|
|
3569
3739
|
}
|
|
3570
|
-
const raw = await
|
|
3740
|
+
const raw = await import_node_fs16.promises.readFile(target, "utf8");
|
|
3571
3741
|
return JSON.parse(raw);
|
|
3572
3742
|
}
|
|
3573
3743
|
function indexEntries(entries) {
|
|
@@ -3635,23 +3805,23 @@ function canonicalJson(value) {
|
|
|
3635
3805
|
|
|
3636
3806
|
// src/projects.ts
|
|
3637
3807
|
init_cjs_shims();
|
|
3638
|
-
var
|
|
3808
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3639
3809
|
function pathsForProject(project, baseDir) {
|
|
3640
3810
|
if (project === DEFAULT_PROJECT) {
|
|
3641
3811
|
return {
|
|
3642
|
-
snapshotPath:
|
|
3643
|
-
errorsPath:
|
|
3644
|
-
staleEventsPath:
|
|
3645
|
-
embeddingsCachePath:
|
|
3646
|
-
policyViolationsPath:
|
|
3812
|
+
snapshotPath: import_node_path28.default.join(baseDir, "graph.json"),
|
|
3813
|
+
errorsPath: import_node_path28.default.join(baseDir, "errors.ndjson"),
|
|
3814
|
+
staleEventsPath: import_node_path28.default.join(baseDir, "stale-events.ndjson"),
|
|
3815
|
+
embeddingsCachePath: import_node_path28.default.join(baseDir, "embeddings.json"),
|
|
3816
|
+
policyViolationsPath: import_node_path28.default.join(baseDir, "policy-violations.ndjson")
|
|
3647
3817
|
};
|
|
3648
3818
|
}
|
|
3649
3819
|
return {
|
|
3650
|
-
snapshotPath:
|
|
3651
|
-
errorsPath:
|
|
3652
|
-
staleEventsPath:
|
|
3653
|
-
embeddingsCachePath:
|
|
3654
|
-
policyViolationsPath:
|
|
3820
|
+
snapshotPath: import_node_path28.default.join(baseDir, `${project}.json`),
|
|
3821
|
+
errorsPath: import_node_path28.default.join(baseDir, `errors.${project}.ndjson`),
|
|
3822
|
+
staleEventsPath: import_node_path28.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
3823
|
+
embeddingsCachePath: import_node_path28.default.join(baseDir, `embeddings.${project}.json`),
|
|
3824
|
+
policyViolationsPath: import_node_path28.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3655
3825
|
};
|
|
3656
3826
|
}
|
|
3657
3827
|
var Projects = class {
|
|
@@ -3689,6 +3859,94 @@ function parseExtraProjects(raw) {
|
|
|
3689
3859
|
return raw.split(",").map((p) => p.trim()).filter((p) => p.length > 0 && p !== DEFAULT_PROJECT);
|
|
3690
3860
|
}
|
|
3691
3861
|
|
|
3862
|
+
// src/registry.ts
|
|
3863
|
+
init_cjs_shims();
|
|
3864
|
+
var import_node_fs17 = require("fs");
|
|
3865
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
3866
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
3867
|
+
var import_types18 = require("@neat.is/types");
|
|
3868
|
+
function neatHome() {
|
|
3869
|
+
const override = process.env.NEAT_HOME;
|
|
3870
|
+
if (override && override.length > 0) return import_node_path29.default.resolve(override);
|
|
3871
|
+
return import_node_path29.default.join(import_node_os2.default.homedir(), ".neat");
|
|
3872
|
+
}
|
|
3873
|
+
function registryPath() {
|
|
3874
|
+
return import_node_path29.default.join(neatHome(), "projects.json");
|
|
3875
|
+
}
|
|
3876
|
+
async function readRegistry() {
|
|
3877
|
+
const file = registryPath();
|
|
3878
|
+
let raw;
|
|
3879
|
+
try {
|
|
3880
|
+
raw = await import_node_fs17.promises.readFile(file, "utf8");
|
|
3881
|
+
} catch (err) {
|
|
3882
|
+
if (err.code === "ENOENT") {
|
|
3883
|
+
return { version: 1, projects: [] };
|
|
3884
|
+
}
|
|
3885
|
+
throw err;
|
|
3886
|
+
}
|
|
3887
|
+
const parsed = JSON.parse(raw);
|
|
3888
|
+
return import_types18.RegistryFileSchema.parse(parsed);
|
|
3889
|
+
}
|
|
3890
|
+
async function listProjects() {
|
|
3891
|
+
const reg = await readRegistry();
|
|
3892
|
+
return reg.projects;
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
// src/streaming.ts
|
|
3896
|
+
init_cjs_shims();
|
|
3897
|
+
var SSE_HEARTBEAT_MS = 3e4;
|
|
3898
|
+
var SSE_BACKPRESSURE_CAP = 1e3;
|
|
3899
|
+
function handleSse(req, reply, opts) {
|
|
3900
|
+
const heartbeatMs = opts.heartbeatMs ?? SSE_HEARTBEAT_MS;
|
|
3901
|
+
const backpressureCap = opts.backpressureCap ?? SSE_BACKPRESSURE_CAP;
|
|
3902
|
+
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
3903
|
+
reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
|
|
3904
|
+
reply.raw.setHeader("Connection", "keep-alive");
|
|
3905
|
+
reply.raw.setHeader("X-Accel-Buffering", "no");
|
|
3906
|
+
reply.raw.flushHeaders?.();
|
|
3907
|
+
let pending = 0;
|
|
3908
|
+
let dropped = false;
|
|
3909
|
+
const closeConnection = () => {
|
|
3910
|
+
if (dropped) return;
|
|
3911
|
+
dropped = true;
|
|
3912
|
+
eventBus.off(EVENT_BUS_CHANNEL, listener);
|
|
3913
|
+
clearInterval(heartbeat);
|
|
3914
|
+
if (!reply.raw.writableEnded) reply.raw.end();
|
|
3915
|
+
};
|
|
3916
|
+
const writeFrame = (frame) => {
|
|
3917
|
+
if (dropped) return;
|
|
3918
|
+
if (pending >= backpressureCap) {
|
|
3919
|
+
const errFrame = `event: error
|
|
3920
|
+
data: ${JSON.stringify({ reason: "backpressure" })}
|
|
3921
|
+
|
|
3922
|
+
`;
|
|
3923
|
+
reply.raw.write(errFrame);
|
|
3924
|
+
closeConnection();
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
pending++;
|
|
3928
|
+
reply.raw.write(frame, () => {
|
|
3929
|
+
pending = Math.max(0, pending - 1);
|
|
3930
|
+
});
|
|
3931
|
+
};
|
|
3932
|
+
const listener = (envelope) => {
|
|
3933
|
+
if (envelope.project !== opts.project) return;
|
|
3934
|
+
writeFrame(`event: ${envelope.type}
|
|
3935
|
+
data: ${JSON.stringify(envelope.payload)}
|
|
3936
|
+
|
|
3937
|
+
`);
|
|
3938
|
+
};
|
|
3939
|
+
eventBus.on(EVENT_BUS_CHANNEL, listener);
|
|
3940
|
+
const heartbeat = setInterval(() => {
|
|
3941
|
+
if (dropped) return;
|
|
3942
|
+
reply.raw.write(":heartbeat\n\n");
|
|
3943
|
+
}, heartbeatMs);
|
|
3944
|
+
if (typeof heartbeat.unref === "function") heartbeat.unref();
|
|
3945
|
+
req.raw.on("close", closeConnection);
|
|
3946
|
+
reply.raw.on("close", closeConnection);
|
|
3947
|
+
reply.raw.on("error", closeConnection);
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3692
3950
|
// src/api.ts
|
|
3693
3951
|
function serializeGraph(graph) {
|
|
3694
3952
|
const nodes = [];
|
|
@@ -3737,6 +3995,11 @@ function buildLegacyRegistry(opts) {
|
|
|
3737
3995
|
}
|
|
3738
3996
|
function registerRoutes(scope, ctx) {
|
|
3739
3997
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
3998
|
+
scope.get("/events", (req, reply) => {
|
|
3999
|
+
const proj = resolveProject(registry, req, reply);
|
|
4000
|
+
if (!proj) return;
|
|
4001
|
+
handleSse(req, reply, { project: proj.name });
|
|
4002
|
+
});
|
|
3740
4003
|
scope.get("/health", async (req, reply) => {
|
|
3741
4004
|
const proj = resolveProject(registry, req, reply);
|
|
3742
4005
|
if (!proj) return;
|
|
@@ -3947,7 +4210,7 @@ function registerRoutes(scope, ctx) {
|
|
|
3947
4210
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
3948
4211
|
let violations = await log.readAll();
|
|
3949
4212
|
if (req.query.severity) {
|
|
3950
|
-
const sev =
|
|
4213
|
+
const sev = import_types19.PolicySeveritySchema.safeParse(req.query.severity);
|
|
3951
4214
|
if (!sev.success) {
|
|
3952
4215
|
return reply.code(400).send({
|
|
3953
4216
|
error: "invalid severity",
|
|
@@ -3964,7 +4227,7 @@ function registerRoutes(scope, ctx) {
|
|
|
3964
4227
|
scope.post("/policies/check", async (req, reply) => {
|
|
3965
4228
|
const proj = resolveProject(registry, req, reply);
|
|
3966
4229
|
if (!proj) return;
|
|
3967
|
-
const parsed =
|
|
4230
|
+
const parsed = import_types19.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
3968
4231
|
if (!parsed.success) {
|
|
3969
4232
|
return reply.code(400).send({
|
|
3970
4233
|
error: "invalid /policies/check body",
|
|
@@ -4028,17 +4291,16 @@ async function buildApi(opts) {
|
|
|
4028
4291
|
staleEventsPathFor,
|
|
4029
4292
|
policyFilePathFor
|
|
4030
4293
|
};
|
|
4031
|
-
app.get("/projects", async () =>
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
}));
|
|
4294
|
+
app.get("/projects", async (_req, reply) => {
|
|
4295
|
+
try {
|
|
4296
|
+
return await listProjects();
|
|
4297
|
+
} catch (err) {
|
|
4298
|
+
return reply.code(500).send({
|
|
4299
|
+
error: "failed to read project registry",
|
|
4300
|
+
details: err.message
|
|
4301
|
+
});
|
|
4302
|
+
}
|
|
4303
|
+
});
|
|
4042
4304
|
registerRoutes(app, routeCtx);
|
|
4043
4305
|
await app.register(
|
|
4044
4306
|
async (scope) => {
|
|
@@ -4051,8 +4313,8 @@ async function buildApi(opts) {
|
|
|
4051
4313
|
|
|
4052
4314
|
// src/persist.ts
|
|
4053
4315
|
init_cjs_shims();
|
|
4054
|
-
var
|
|
4055
|
-
var
|
|
4316
|
+
var import_node_fs18 = require("fs");
|
|
4317
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4056
4318
|
var SCHEMA_VERSION = 2;
|
|
4057
4319
|
function migrateV1ToV2(payload) {
|
|
4058
4320
|
const nodes = payload.graph.nodes;
|
|
@@ -4066,7 +4328,7 @@ function migrateV1ToV2(payload) {
|
|
|
4066
4328
|
return { ...payload, schemaVersion: 2 };
|
|
4067
4329
|
}
|
|
4068
4330
|
async function ensureDir(filePath) {
|
|
4069
|
-
await
|
|
4331
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
|
|
4070
4332
|
}
|
|
4071
4333
|
async function saveGraphToDisk(graph, outPath) {
|
|
4072
4334
|
await ensureDir(outPath);
|
|
@@ -4076,13 +4338,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4076
4338
|
graph: graph.export()
|
|
4077
4339
|
};
|
|
4078
4340
|
const tmp = `${outPath}.tmp`;
|
|
4079
|
-
await
|
|
4080
|
-
await
|
|
4341
|
+
await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4342
|
+
await import_node_fs18.promises.rename(tmp, outPath);
|
|
4081
4343
|
}
|
|
4082
4344
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4083
4345
|
let raw;
|
|
4084
4346
|
try {
|
|
4085
|
-
raw = await
|
|
4347
|
+
raw = await import_node_fs18.promises.readFile(outPath, "utf8");
|
|
4086
4348
|
} catch (err) {
|
|
4087
4349
|
if (err.code === "ENOENT") return;
|
|
4088
4350
|
throw err;
|
|
@@ -4139,8 +4401,8 @@ init_otel_grpc();
|
|
|
4139
4401
|
|
|
4140
4402
|
// src/search.ts
|
|
4141
4403
|
init_cjs_shims();
|
|
4142
|
-
var
|
|
4143
|
-
var
|
|
4404
|
+
var import_node_fs19 = require("fs");
|
|
4405
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4144
4406
|
var import_node_crypto = require("crypto");
|
|
4145
4407
|
var DEFAULT_LIMIT = 10;
|
|
4146
4408
|
var NOMIC_DIM = 768;
|
|
@@ -4270,7 +4532,7 @@ async function pickEmbedder() {
|
|
|
4270
4532
|
}
|
|
4271
4533
|
async function readCache(cachePath) {
|
|
4272
4534
|
try {
|
|
4273
|
-
const raw = await
|
|
4535
|
+
const raw = await import_node_fs19.promises.readFile(cachePath, "utf8");
|
|
4274
4536
|
const parsed = JSON.parse(raw);
|
|
4275
4537
|
if (parsed.version !== 1) return null;
|
|
4276
4538
|
return parsed;
|
|
@@ -4279,8 +4541,8 @@ async function readCache(cachePath) {
|
|
|
4279
4541
|
}
|
|
4280
4542
|
}
|
|
4281
4543
|
async function writeCache(cachePath, cache) {
|
|
4282
|
-
await
|
|
4283
|
-
await
|
|
4544
|
+
await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(cachePath), { recursive: true });
|
|
4545
|
+
await import_node_fs19.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
4284
4546
|
}
|
|
4285
4547
|
var VectorIndex = class {
|
|
4286
4548
|
constructor(embedder, cachePath) {
|
|
@@ -4459,14 +4721,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
4459
4721
|
async function main() {
|
|
4460
4722
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
4461
4723
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
4462
|
-
const baseDir = baseDirEnv ?
|
|
4463
|
-
const defaultScanPath =
|
|
4724
|
+
const baseDir = baseDirEnv ? import_node_path34.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path34.default.resolve(import_node_path34.default.dirname(legacyOutPath)) : import_node_path34.default.resolve("./neat-out");
|
|
4725
|
+
const defaultScanPath = import_node_path34.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
4464
4726
|
const registry = new Projects();
|
|
4465
4727
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
4466
4728
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
4467
4729
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
4468
4730
|
const projectScan = process.env[envKey];
|
|
4469
|
-
await bootProject(registry, name, projectScan ?
|
|
4731
|
+
await bootProject(registry, name, projectScan ? import_node_path34.default.resolve(projectScan) : void 0, baseDir);
|
|
4470
4732
|
}
|
|
4471
4733
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
4472
4734
|
const port = Number(process.env.PORT ?? 8080);
|