@neat.is/core 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FTKDVKBC.js → chunk-5KX7EI4F.js} +2 -2
- package/dist/{chunk-HWO746IM.js → chunk-BVF7MVR5.js} +2 -2
- package/dist/{chunk-GAYTAGEH.js → chunk-IRPH6KL4.js} +266 -146
- package/dist/chunk-IRPH6KL4.js.map +1 -0
- package/dist/cli.cjs +356 -235
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +287 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/neatd.cjs +271 -151
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +277 -156
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-GAYTAGEH.js.map +0 -1
- /package/dist/{chunk-FTKDVKBC.js.map → chunk-5KX7EI4F.js.map} +0 -0
- /package/dist/{chunk-HWO746IM.js.map → chunk-BVF7MVR5.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function resolveProtoRoot() {
|
|
103
|
-
const here =
|
|
104
|
-
return
|
|
103
|
+
const here = import_node_path31.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
104
|
+
return import_node_path31.default.resolve(here, "..", "proto");
|
|
105
105
|
}
|
|
106
106
|
function loadTraceService() {
|
|
107
107
|
const protoRoot = resolveProtoRoot();
|
|
@@ -156,13 +156,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
156
156
|
})
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
var import_node_url,
|
|
159
|
+
var import_node_url, import_node_path31, grpc, protoLoader;
|
|
160
160
|
var init_otel_grpc = __esm({
|
|
161
161
|
"src/otel-grpc.ts"() {
|
|
162
162
|
"use strict";
|
|
163
163
|
init_cjs_shims();
|
|
164
164
|
import_node_url = require("url");
|
|
165
|
-
|
|
165
|
+
import_node_path31 = __toESM(require("path"), 1);
|
|
166
166
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
167
167
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
168
168
|
init_otel();
|
|
@@ -259,10 +259,10 @@ function parseOtlpRequest(body) {
|
|
|
259
259
|
}
|
|
260
260
|
function loadProtobufDecoder() {
|
|
261
261
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
262
|
-
const here =
|
|
263
|
-
const protoRoot =
|
|
262
|
+
const here = import_node_path32.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
263
|
+
const protoRoot = import_node_path32.default.resolve(here, "..", "proto");
|
|
264
264
|
const root = new import_protobufjs.default.Root();
|
|
265
|
-
root.resolvePath = (_origin, target) =>
|
|
265
|
+
root.resolvePath = (_origin, target) => import_node_path32.default.resolve(protoRoot, target);
|
|
266
266
|
root.loadSync(
|
|
267
267
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
268
268
|
{ keepCase: true }
|
|
@@ -362,12 +362,12 @@ function logSpanHandler(span) {
|
|
|
362
362
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
363
363
|
);
|
|
364
364
|
}
|
|
365
|
-
var
|
|
365
|
+
var import_node_path32, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
366
366
|
var init_otel = __esm({
|
|
367
367
|
"src/otel.ts"() {
|
|
368
368
|
"use strict";
|
|
369
369
|
init_cjs_shims();
|
|
370
|
-
|
|
370
|
+
import_node_path32 = __toESM(require("path"), 1);
|
|
371
371
|
import_node_url2 = require("url");
|
|
372
372
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
373
373
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -876,19 +876,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
876
876
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
877
877
|
let best = { path: [start], edges: [] };
|
|
878
878
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
879
|
-
function step(node,
|
|
880
|
-
if (
|
|
881
|
-
best = { path: [...
|
|
879
|
+
function step(node, path34, edges) {
|
|
880
|
+
if (path34.length > best.path.length) {
|
|
881
|
+
best = { path: [...path34], edges: [...edges] };
|
|
882
882
|
}
|
|
883
|
-
if (
|
|
883
|
+
if (path34.length - 1 >= maxDepth) return;
|
|
884
884
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
885
885
|
for (const [srcId, edge] of incoming) {
|
|
886
886
|
if (visited.has(srcId)) continue;
|
|
887
887
|
visited.add(srcId);
|
|
888
|
-
|
|
888
|
+
path34.push(srcId);
|
|
889
889
|
edges.push(edge);
|
|
890
|
-
step(srcId,
|
|
891
|
-
|
|
890
|
+
step(srcId, path34, edges);
|
|
891
|
+
path34.pop();
|
|
892
892
|
edges.pop();
|
|
893
893
|
visited.delete(srcId);
|
|
894
894
|
}
|
|
@@ -1902,10 +1902,10 @@ async function readErrorEvents(errorsPath) {
|
|
|
1902
1902
|
|
|
1903
1903
|
// src/extract/services.ts
|
|
1904
1904
|
init_cjs_shims();
|
|
1905
|
-
var
|
|
1906
|
-
var
|
|
1905
|
+
var import_node_fs7 = require("fs");
|
|
1906
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1907
1907
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1908
|
-
var
|
|
1908
|
+
var import_minimatch2 = require("minimatch");
|
|
1909
1909
|
var import_types5 = require("@neat.is/types");
|
|
1910
1910
|
|
|
1911
1911
|
// src/extract/shared.ts
|
|
@@ -2018,6 +2018,73 @@ function pythonToPackage(service) {
|
|
|
2018
2018
|
};
|
|
2019
2019
|
}
|
|
2020
2020
|
|
|
2021
|
+
// src/extract/owners.ts
|
|
2022
|
+
init_cjs_shims();
|
|
2023
|
+
var import_node_fs6 = require("fs");
|
|
2024
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2025
|
+
var import_minimatch = require("minimatch");
|
|
2026
|
+
async function loadCodeowners(scanPath) {
|
|
2027
|
+
const candidates = [
|
|
2028
|
+
import_node_path6.default.join(scanPath, "CODEOWNERS"),
|
|
2029
|
+
import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2030
|
+
];
|
|
2031
|
+
for (const file of candidates) {
|
|
2032
|
+
if (await exists(file)) {
|
|
2033
|
+
const raw = await import_node_fs6.promises.readFile(file, "utf8");
|
|
2034
|
+
return parseCodeowners(raw);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
function parseCodeowners(raw) {
|
|
2040
|
+
const rules = [];
|
|
2041
|
+
for (const line of raw.split("\n")) {
|
|
2042
|
+
const trimmed = line.trim();
|
|
2043
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2044
|
+
const match = /^(\S+)\s+(.+)$/.exec(trimmed);
|
|
2045
|
+
if (!match) continue;
|
|
2046
|
+
rules.push({ pattern: match[1], owners: match[2].trim() });
|
|
2047
|
+
}
|
|
2048
|
+
return { rules };
|
|
2049
|
+
}
|
|
2050
|
+
function matchOwner(file, repoPath) {
|
|
2051
|
+
const normalized = repoPath.split(import_node_path6.default.sep).join("/");
|
|
2052
|
+
for (const rule of file.rules) {
|
|
2053
|
+
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2054
|
+
}
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
function matchesPattern(rawPattern, repoPath) {
|
|
2058
|
+
let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
|
|
2059
|
+
if (pattern === "*") return !repoPath.includes("/");
|
|
2060
|
+
if (pattern === "**" || pattern === "") return true;
|
|
2061
|
+
if (pattern.endsWith("/")) pattern = pattern + "**";
|
|
2062
|
+
if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
|
|
2063
|
+
if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
|
|
2064
|
+
return false;
|
|
2065
|
+
}
|
|
2066
|
+
async function readPackageJsonAuthor(serviceDir) {
|
|
2067
|
+
const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
|
|
2068
|
+
if (!await exists(pkgPath)) return null;
|
|
2069
|
+
try {
|
|
2070
|
+
const pkg = await readJson(pkgPath);
|
|
2071
|
+
if (!pkg.author) return null;
|
|
2072
|
+
if (typeof pkg.author === "string") return pkg.author;
|
|
2073
|
+
if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
|
|
2074
|
+
return null;
|
|
2075
|
+
} catch {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
2080
|
+
if (codeowners && repoPath !== void 0) {
|
|
2081
|
+
const owner = matchOwner(codeowners, repoPath);
|
|
2082
|
+
if (owner) return owner;
|
|
2083
|
+
}
|
|
2084
|
+
const author = await readPackageJsonAuthor(serviceDir);
|
|
2085
|
+
return author ?? void 0;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2021
2088
|
// src/extract/services.ts
|
|
2022
2089
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
2023
2090
|
function parseScanDepth() {
|
|
@@ -2034,21 +2101,21 @@ function workspaceGlobs(pkg) {
|
|
|
2034
2101
|
return null;
|
|
2035
2102
|
}
|
|
2036
2103
|
async function loadGitignore(scanPath) {
|
|
2037
|
-
const gitignorePath =
|
|
2104
|
+
const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
|
|
2038
2105
|
if (!await exists(gitignorePath)) return null;
|
|
2039
|
-
const raw = await
|
|
2106
|
+
const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
|
|
2040
2107
|
return (0, import_ignore.default)().add(raw);
|
|
2041
2108
|
}
|
|
2042
2109
|
async function walkDirs(start, scanPath, options, visit) {
|
|
2043
2110
|
async function recurse(current, depth) {
|
|
2044
2111
|
if (depth > options.maxDepth) return;
|
|
2045
|
-
const entries = await
|
|
2112
|
+
const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2046
2113
|
for (const entry of entries) {
|
|
2047
2114
|
if (!entry.isDirectory()) continue;
|
|
2048
2115
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2049
|
-
const child =
|
|
2116
|
+
const child = import_node_path7.default.join(current, entry.name);
|
|
2050
2117
|
if (options.ig) {
|
|
2051
|
-
const rel =
|
|
2118
|
+
const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
|
|
2052
2119
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2053
2120
|
}
|
|
2054
2121
|
await visit(child);
|
|
@@ -2063,8 +2130,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2063
2130
|
for (const raw of globs) {
|
|
2064
2131
|
const pattern = raw.replace(/^\.\//, "");
|
|
2065
2132
|
if (!pattern.includes("*")) {
|
|
2066
|
-
const candidate =
|
|
2067
|
-
if (await exists(
|
|
2133
|
+
const candidate = import_node_path7.default.join(scanPath, pattern);
|
|
2134
|
+
if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2068
2135
|
continue;
|
|
2069
2136
|
}
|
|
2070
2137
|
const segments = pattern.split("/");
|
|
@@ -2073,13 +2140,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2073
2140
|
if (seg.includes("*")) break;
|
|
2074
2141
|
staticSegments.push(seg);
|
|
2075
2142
|
}
|
|
2076
|
-
const start =
|
|
2143
|
+
const start = import_node_path7.default.join(scanPath, ...staticSegments);
|
|
2077
2144
|
if (!await exists(start)) continue;
|
|
2078
2145
|
const hasDoubleStar = pattern.includes("**");
|
|
2079
2146
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2080
2147
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2081
|
-
const rel =
|
|
2082
|
-
if ((0,
|
|
2148
|
+
const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
|
|
2149
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
2083
2150
|
found.add(dir);
|
|
2084
2151
|
}
|
|
2085
2152
|
});
|
|
@@ -2087,9 +2154,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2087
2154
|
return [...found];
|
|
2088
2155
|
}
|
|
2089
2156
|
async function discoverNodeService(scanPath, dir) {
|
|
2090
|
-
const pkgPath =
|
|
2157
|
+
const pkgPath = import_node_path7.default.join(dir, "package.json");
|
|
2091
2158
|
if (!await exists(pkgPath)) return null;
|
|
2092
|
-
|
|
2159
|
+
let pkg;
|
|
2160
|
+
try {
|
|
2161
|
+
pkg = await readJson(pkgPath);
|
|
2162
|
+
} catch (err) {
|
|
2163
|
+
console.warn(
|
|
2164
|
+
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2165
|
+
);
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2093
2168
|
if (!pkg.name) return null;
|
|
2094
2169
|
const node = {
|
|
2095
2170
|
id: (0, import_types5.serviceId)(pkg.name),
|
|
@@ -2098,7 +2173,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2098
2173
|
language: "javascript",
|
|
2099
2174
|
version: pkg.version,
|
|
2100
2175
|
dependencies: pkg.dependencies ?? {},
|
|
2101
|
-
repoPath:
|
|
2176
|
+
repoPath: import_node_path7.default.relative(scanPath, dir),
|
|
2102
2177
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2103
2178
|
};
|
|
2104
2179
|
return { pkg, dir, node };
|
|
@@ -2114,13 +2189,22 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2114
2189
|
language: "python",
|
|
2115
2190
|
version: py.version,
|
|
2116
2191
|
dependencies: py.dependencies,
|
|
2117
|
-
repoPath:
|
|
2192
|
+
repoPath: import_node_path7.default.relative(scanPath, dir)
|
|
2118
2193
|
};
|
|
2119
2194
|
return { pkg, dir, node };
|
|
2120
2195
|
}
|
|
2121
2196
|
async function discoverServices(scanPath) {
|
|
2122
|
-
const rootPkgPath =
|
|
2123
|
-
|
|
2197
|
+
const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
|
|
2198
|
+
let rootPkg = null;
|
|
2199
|
+
if (await exists(rootPkgPath)) {
|
|
2200
|
+
try {
|
|
2201
|
+
rootPkg = await readJson(rootPkgPath);
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
console.warn(
|
|
2204
|
+
`[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2124
2208
|
const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
|
|
2125
2209
|
const candidateDirs = [];
|
|
2126
2210
|
if (wsGlobs) {
|
|
@@ -2133,9 +2217,9 @@ async function discoverServices(scanPath) {
|
|
|
2133
2217
|
scanPath,
|
|
2134
2218
|
{ maxDepth: parseScanDepth(), ig },
|
|
2135
2219
|
async (dir) => {
|
|
2136
|
-
if (await exists(
|
|
2220
|
+
if (await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
2137
2221
|
candidateDirs.push(dir);
|
|
2138
|
-
} else if (await exists(
|
|
2222
|
+
} 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"))) {
|
|
2139
2223
|
candidateDirs.push(dir);
|
|
2140
2224
|
}
|
|
2141
2225
|
}
|
|
@@ -2149,8 +2233,8 @@ async function discoverServices(scanPath) {
|
|
|
2149
2233
|
if (!service) continue;
|
|
2150
2234
|
const existingDir = seen.get(service.node.name);
|
|
2151
2235
|
if (existingDir !== void 0) {
|
|
2152
|
-
const a =
|
|
2153
|
-
const b =
|
|
2236
|
+
const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
|
|
2237
|
+
const b = import_node_path7.default.relative(scanPath, dir) || ".";
|
|
2154
2238
|
console.warn(
|
|
2155
2239
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2156
2240
|
);
|
|
@@ -2159,6 +2243,11 @@ async function discoverServices(scanPath) {
|
|
|
2159
2243
|
seen.set(service.node.name, dir);
|
|
2160
2244
|
out.push(service);
|
|
2161
2245
|
}
|
|
2246
|
+
const codeowners = await loadCodeowners(scanPath);
|
|
2247
|
+
for (const service of out) {
|
|
2248
|
+
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
2249
|
+
if (owner !== void 0) service.node.owner = owner;
|
|
2250
|
+
}
|
|
2162
2251
|
return out;
|
|
2163
2252
|
}
|
|
2164
2253
|
function addServiceNodes(graph, services) {
|
|
@@ -2182,8 +2271,8 @@ function addServiceNodes(graph, services) {
|
|
|
2182
2271
|
|
|
2183
2272
|
// src/extract/aliases.ts
|
|
2184
2273
|
init_cjs_shims();
|
|
2185
|
-
var
|
|
2186
|
-
var
|
|
2274
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2275
|
+
var import_node_fs8 = require("fs");
|
|
2187
2276
|
var import_yaml2 = require("yaml");
|
|
2188
2277
|
var import_types6 = require("@neat.is/types");
|
|
2189
2278
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -2210,21 +2299,29 @@ function indexServicesByName(services) {
|
|
|
2210
2299
|
const map = /* @__PURE__ */ new Map();
|
|
2211
2300
|
for (const s of services) {
|
|
2212
2301
|
map.set(s.node.name, s.node.id);
|
|
2213
|
-
map.set(
|
|
2302
|
+
map.set(import_node_path8.default.basename(s.dir), s.node.id);
|
|
2214
2303
|
}
|
|
2215
2304
|
return map;
|
|
2216
2305
|
}
|
|
2217
2306
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2218
2307
|
let composePath = null;
|
|
2219
2308
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2220
|
-
const abs =
|
|
2309
|
+
const abs = import_node_path8.default.join(scanPath, name);
|
|
2221
2310
|
if (await exists(abs)) {
|
|
2222
2311
|
composePath = abs;
|
|
2223
2312
|
break;
|
|
2224
2313
|
}
|
|
2225
2314
|
}
|
|
2226
2315
|
if (!composePath) return;
|
|
2227
|
-
|
|
2316
|
+
let compose;
|
|
2317
|
+
try {
|
|
2318
|
+
compose = await readYaml(composePath);
|
|
2319
|
+
} catch (err) {
|
|
2320
|
+
console.warn(
|
|
2321
|
+
`[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
|
|
2322
|
+
);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2228
2325
|
if (!compose?.services) return;
|
|
2229
2326
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
2230
2327
|
const serviceId3 = serviceIndex.get(composeName);
|
|
@@ -2263,9 +2360,17 @@ function parseDockerfileLabels(content) {
|
|
|
2263
2360
|
}
|
|
2264
2361
|
async function collectDockerfileAliases(graph, services) {
|
|
2265
2362
|
for (const service of services) {
|
|
2266
|
-
const dockerfilePath =
|
|
2363
|
+
const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
|
|
2267
2364
|
if (!await exists(dockerfilePath)) continue;
|
|
2268
|
-
|
|
2365
|
+
let content;
|
|
2366
|
+
try {
|
|
2367
|
+
content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
|
|
2368
|
+
} catch (err) {
|
|
2369
|
+
console.warn(
|
|
2370
|
+
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2371
|
+
);
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2269
2374
|
const aliases = parseDockerfileLabels(content);
|
|
2270
2375
|
if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
|
|
2271
2376
|
}
|
|
@@ -2273,13 +2378,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2273
2378
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2274
2379
|
if (depth > max) return [];
|
|
2275
2380
|
const out = [];
|
|
2276
|
-
const entries = await
|
|
2381
|
+
const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2277
2382
|
for (const entry of entries) {
|
|
2278
2383
|
if (entry.isDirectory()) {
|
|
2279
2384
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2280
|
-
out.push(...await walkYamlFiles(
|
|
2281
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2282
|
-
out.push(
|
|
2385
|
+
out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry.name), depth + 1, max));
|
|
2386
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry.name))) {
|
|
2387
|
+
out.push(import_node_path8.default.join(start, entry.name));
|
|
2283
2388
|
}
|
|
2284
2389
|
}
|
|
2285
2390
|
return out;
|
|
@@ -2306,7 +2411,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2306
2411
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2307
2412
|
const files = await walkYamlFiles(scanPath);
|
|
2308
2413
|
for (const file of files) {
|
|
2309
|
-
const content = await
|
|
2414
|
+
const content = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2310
2415
|
let docs;
|
|
2311
2416
|
try {
|
|
2312
2417
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2331,14 +2436,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2331
2436
|
|
|
2332
2437
|
// src/extract/databases/index.ts
|
|
2333
2438
|
init_cjs_shims();
|
|
2334
|
-
var
|
|
2439
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2335
2440
|
var import_types7 = require("@neat.is/types");
|
|
2336
2441
|
|
|
2337
2442
|
// src/extract/databases/db-config-yaml.ts
|
|
2338
2443
|
init_cjs_shims();
|
|
2339
|
-
var
|
|
2444
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2340
2445
|
async function parse(serviceDir) {
|
|
2341
|
-
const yamlPath =
|
|
2446
|
+
const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
|
|
2342
2447
|
if (!await exists(yamlPath)) return [];
|
|
2343
2448
|
const raw = await readYaml(yamlPath);
|
|
2344
2449
|
return [
|
|
@@ -2356,13 +2461,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2356
2461
|
|
|
2357
2462
|
// src/extract/databases/dotenv.ts
|
|
2358
2463
|
init_cjs_shims();
|
|
2359
|
-
var
|
|
2360
|
-
var
|
|
2464
|
+
var import_node_fs10 = require("fs");
|
|
2465
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2361
2466
|
|
|
2362
2467
|
// src/extract/databases/shared.ts
|
|
2363
2468
|
init_cjs_shims();
|
|
2364
|
-
var
|
|
2365
|
-
var
|
|
2469
|
+
var import_node_fs9 = require("fs");
|
|
2470
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2366
2471
|
function schemeToEngine(scheme) {
|
|
2367
2472
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2368
2473
|
switch (s) {
|
|
@@ -2401,14 +2506,14 @@ function parseConnectionString(url) {
|
|
|
2401
2506
|
}
|
|
2402
2507
|
async function readIfExists(filePath) {
|
|
2403
2508
|
try {
|
|
2404
|
-
return await
|
|
2509
|
+
return await import_node_fs9.promises.readFile(filePath, "utf8");
|
|
2405
2510
|
} catch {
|
|
2406
2511
|
return null;
|
|
2407
2512
|
}
|
|
2408
2513
|
}
|
|
2409
2514
|
async function findFirst(serviceDir, candidates) {
|
|
2410
2515
|
for (const rel of candidates) {
|
|
2411
|
-
const abs =
|
|
2516
|
+
const abs = import_node_path10.default.join(serviceDir, rel);
|
|
2412
2517
|
const content = await readIfExists(abs);
|
|
2413
2518
|
if (content !== null) return abs;
|
|
2414
2519
|
}
|
|
@@ -2459,15 +2564,15 @@ function parseDotenvLine(line) {
|
|
|
2459
2564
|
return { key, value };
|
|
2460
2565
|
}
|
|
2461
2566
|
async function parse2(serviceDir) {
|
|
2462
|
-
const entries = await
|
|
2567
|
+
const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2463
2568
|
const configs = [];
|
|
2464
2569
|
const seen = /* @__PURE__ */ new Set();
|
|
2465
2570
|
for (const entry of entries) {
|
|
2466
2571
|
if (!entry.isFile()) continue;
|
|
2467
2572
|
const match = isConfigFile(entry.name);
|
|
2468
2573
|
if (!match.match || match.fileType !== "env") continue;
|
|
2469
|
-
const filePath =
|
|
2470
|
-
const content = await
|
|
2574
|
+
const filePath = import_node_path11.default.join(serviceDir, entry.name);
|
|
2575
|
+
const content = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2471
2576
|
for (const line of content.split("\n")) {
|
|
2472
2577
|
const parsed = parseDotenvLine(line);
|
|
2473
2578
|
if (!parsed) continue;
|
|
@@ -2486,9 +2591,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2486
2591
|
|
|
2487
2592
|
// src/extract/databases/prisma.ts
|
|
2488
2593
|
init_cjs_shims();
|
|
2489
|
-
var
|
|
2594
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2490
2595
|
async function parse3(serviceDir) {
|
|
2491
|
-
const schemaPath =
|
|
2596
|
+
const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2492
2597
|
const content = await readIfExists(schemaPath);
|
|
2493
2598
|
if (!content) return [];
|
|
2494
2599
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2620,10 +2725,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2620
2725
|
|
|
2621
2726
|
// src/extract/databases/ormconfig.ts
|
|
2622
2727
|
init_cjs_shims();
|
|
2623
|
-
var
|
|
2728
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2624
2729
|
async function parse6(serviceDir) {
|
|
2625
2730
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2626
|
-
const abs =
|
|
2731
|
+
const abs = import_node_path13.default.join(serviceDir, candidate);
|
|
2627
2732
|
if (!await exists(abs)) continue;
|
|
2628
2733
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2629
2734
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2683,9 +2788,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2683
2788
|
|
|
2684
2789
|
// src/extract/databases/sequelize.ts
|
|
2685
2790
|
init_cjs_shims();
|
|
2686
|
-
var
|
|
2791
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2687
2792
|
async function parse8(serviceDir) {
|
|
2688
|
-
const configPath =
|
|
2793
|
+
const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
|
|
2689
2794
|
if (!await exists(configPath)) return [];
|
|
2690
2795
|
const raw = await readJson(configPath);
|
|
2691
2796
|
const out = [];
|
|
@@ -2712,7 +2817,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2712
2817
|
|
|
2713
2818
|
// src/extract/databases/docker-compose.ts
|
|
2714
2819
|
init_cjs_shims();
|
|
2715
|
-
var
|
|
2820
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2716
2821
|
function portFromService(svc) {
|
|
2717
2822
|
for (const raw of svc.ports ?? []) {
|
|
2718
2823
|
const str = String(raw);
|
|
@@ -2739,7 +2844,7 @@ function databaseFromEnv(svc) {
|
|
|
2739
2844
|
}
|
|
2740
2845
|
async function parse9(serviceDir) {
|
|
2741
2846
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2742
|
-
const abs =
|
|
2847
|
+
const abs = import_node_path15.default.join(serviceDir, name);
|
|
2743
2848
|
if (!await exists(abs)) continue;
|
|
2744
2849
|
const raw = await readYaml(abs);
|
|
2745
2850
|
if (!raw?.services) return [];
|
|
@@ -2919,7 +3024,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2919
3024
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
2920
3025
|
...config.sourceFile ? {
|
|
2921
3026
|
evidence: {
|
|
2922
|
-
file:
|
|
3027
|
+
file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
|
|
2923
3028
|
}
|
|
2924
3029
|
} : {}
|
|
2925
3030
|
};
|
|
@@ -2947,15 +3052,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2947
3052
|
|
|
2948
3053
|
// src/extract/configs.ts
|
|
2949
3054
|
init_cjs_shims();
|
|
2950
|
-
var
|
|
2951
|
-
var
|
|
3055
|
+
var import_node_fs11 = require("fs");
|
|
3056
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2952
3057
|
var import_types8 = require("@neat.is/types");
|
|
2953
3058
|
async function walkConfigFiles(dir) {
|
|
2954
3059
|
const out = [];
|
|
2955
3060
|
async function walk(current) {
|
|
2956
|
-
const entries = await
|
|
3061
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
|
|
2957
3062
|
for (const entry of entries) {
|
|
2958
|
-
const full =
|
|
3063
|
+
const full = import_node_path17.default.join(current, entry.name);
|
|
2959
3064
|
if (entry.isDirectory()) {
|
|
2960
3065
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2961
3066
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -2972,13 +3077,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2972
3077
|
for (const service of services) {
|
|
2973
3078
|
const configFiles = await walkConfigFiles(service.dir);
|
|
2974
3079
|
for (const file of configFiles) {
|
|
2975
|
-
const relPath =
|
|
3080
|
+
const relPath = import_node_path17.default.relative(scanPath, file);
|
|
2976
3081
|
const node = {
|
|
2977
3082
|
id: (0, import_types8.configId)(relPath),
|
|
2978
3083
|
type: import_types8.NodeType.ConfigNode,
|
|
2979
|
-
name:
|
|
3084
|
+
name: import_node_path17.default.basename(file),
|
|
2980
3085
|
path: relPath,
|
|
2981
|
-
fileType: isConfigFile(
|
|
3086
|
+
fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
|
|
2982
3087
|
};
|
|
2983
3088
|
if (!graph.hasNode(node.id)) {
|
|
2984
3089
|
graph.addNode(node.id, node);
|
|
@@ -2990,7 +3095,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2990
3095
|
target: node.id,
|
|
2991
3096
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
2992
3097
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
2993
|
-
evidence: { file: relPath.split(
|
|
3098
|
+
evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
|
|
2994
3099
|
};
|
|
2995
3100
|
if (!graph.hasEdge(edge.id)) {
|
|
2996
3101
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3007,7 +3112,7 @@ var import_types14 = require("@neat.is/types");
|
|
|
3007
3112
|
|
|
3008
3113
|
// src/extract/calls/http.ts
|
|
3009
3114
|
init_cjs_shims();
|
|
3010
|
-
var
|
|
3115
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3011
3116
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3012
3117
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3013
3118
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
@@ -3015,17 +3120,17 @@ var import_types9 = require("@neat.is/types");
|
|
|
3015
3120
|
|
|
3016
3121
|
// src/extract/calls/shared.ts
|
|
3017
3122
|
init_cjs_shims();
|
|
3018
|
-
var
|
|
3019
|
-
var
|
|
3123
|
+
var import_node_fs12 = require("fs");
|
|
3124
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3020
3125
|
async function walkSourceFiles(dir) {
|
|
3021
3126
|
const out = [];
|
|
3022
3127
|
async function walk(current) {
|
|
3023
|
-
const entries = await
|
|
3128
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3024
3129
|
for (const entry of entries) {
|
|
3025
|
-
const full =
|
|
3130
|
+
const full = import_node_path18.default.join(current, entry.name);
|
|
3026
3131
|
if (entry.isDirectory()) {
|
|
3027
3132
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
3028
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3133
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry.name))) {
|
|
3029
3134
|
out.push(full);
|
|
3030
3135
|
}
|
|
3031
3136
|
}
|
|
@@ -3038,7 +3143,7 @@ async function loadSourceFiles(dir) {
|
|
|
3038
3143
|
const out = [];
|
|
3039
3144
|
for (const p of paths) {
|
|
3040
3145
|
try {
|
|
3041
|
-
const content = await
|
|
3146
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
3042
3147
|
out.push({ path: p, content });
|
|
3043
3148
|
} catch {
|
|
3044
3149
|
}
|
|
@@ -3094,9 +3199,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3094
3199
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3095
3200
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3096
3201
|
for (const service of services) {
|
|
3097
|
-
knownHosts.add(
|
|
3202
|
+
knownHosts.add(import_node_path19.default.basename(service.dir));
|
|
3098
3203
|
knownHosts.add(service.pkg.name);
|
|
3099
|
-
hostToNodeId.set(
|
|
3204
|
+
hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
|
|
3100
3205
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3101
3206
|
}
|
|
3102
3207
|
let edgesAdded = 0;
|
|
@@ -3104,7 +3209,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3104
3209
|
const files = await loadSourceFiles(service.dir);
|
|
3105
3210
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3106
3211
|
for (const file of files) {
|
|
3107
|
-
const parser =
|
|
3212
|
+
const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3108
3213
|
let targets;
|
|
3109
3214
|
try {
|
|
3110
3215
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3132,7 +3237,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3132
3237
|
type: import_types9.EdgeType.CALLS,
|
|
3133
3238
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3134
3239
|
evidence: {
|
|
3135
|
-
file:
|
|
3240
|
+
file: import_node_path19.default.relative(service.dir, evidenceFile.file),
|
|
3136
3241
|
line,
|
|
3137
3242
|
snippet: snippet(fileContent, line)
|
|
3138
3243
|
}
|
|
@@ -3148,7 +3253,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3148
3253
|
|
|
3149
3254
|
// src/extract/calls/kafka.ts
|
|
3150
3255
|
init_cjs_shims();
|
|
3151
|
-
var
|
|
3256
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3152
3257
|
var import_types10 = require("@neat.is/types");
|
|
3153
3258
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3154
3259
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3175,7 +3280,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3175
3280
|
kind: "kafka-topic",
|
|
3176
3281
|
edgeType,
|
|
3177
3282
|
evidence: {
|
|
3178
|
-
file:
|
|
3283
|
+
file: import_node_path20.default.relative(serviceDir, file.path),
|
|
3179
3284
|
line,
|
|
3180
3285
|
snippet: snippet(file.content, line)
|
|
3181
3286
|
}
|
|
@@ -3188,7 +3293,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3188
3293
|
|
|
3189
3294
|
// src/extract/calls/redis.ts
|
|
3190
3295
|
init_cjs_shims();
|
|
3191
|
-
var
|
|
3296
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3192
3297
|
var import_types11 = require("@neat.is/types");
|
|
3193
3298
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3194
3299
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3207,7 +3312,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3207
3312
|
kind: "redis",
|
|
3208
3313
|
edgeType: "CALLS",
|
|
3209
3314
|
evidence: {
|
|
3210
|
-
file:
|
|
3315
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3211
3316
|
line,
|
|
3212
3317
|
snippet: snippet(file.content, line)
|
|
3213
3318
|
}
|
|
@@ -3218,7 +3323,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3218
3323
|
|
|
3219
3324
|
// src/extract/calls/aws.ts
|
|
3220
3325
|
init_cjs_shims();
|
|
3221
|
-
var
|
|
3326
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3222
3327
|
var import_types12 = require("@neat.is/types");
|
|
3223
3328
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3224
3329
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3248,7 +3353,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3248
3353
|
kind,
|
|
3249
3354
|
edgeType: "CALLS",
|
|
3250
3355
|
evidence: {
|
|
3251
|
-
file:
|
|
3356
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3252
3357
|
line,
|
|
3253
3358
|
snippet: snippet(file.content, line)
|
|
3254
3359
|
}
|
|
@@ -3273,7 +3378,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3273
3378
|
|
|
3274
3379
|
// src/extract/calls/grpc.ts
|
|
3275
3380
|
init_cjs_shims();
|
|
3276
|
-
var
|
|
3381
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3277
3382
|
var import_types13 = require("@neat.is/types");
|
|
3278
3383
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3279
3384
|
function isLikelyAddress(value) {
|
|
@@ -3298,7 +3403,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3298
3403
|
kind: "grpc-service",
|
|
3299
3404
|
edgeType: "CALLS",
|
|
3300
3405
|
evidence: {
|
|
3301
|
-
file:
|
|
3406
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3302
3407
|
line,
|
|
3303
3408
|
snippet: snippet(file.content, line)
|
|
3304
3409
|
}
|
|
@@ -3378,7 +3483,7 @@ init_cjs_shims();
|
|
|
3378
3483
|
|
|
3379
3484
|
// src/extract/infra/docker-compose.ts
|
|
3380
3485
|
init_cjs_shims();
|
|
3381
|
-
var
|
|
3486
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3382
3487
|
var import_types16 = require("@neat.is/types");
|
|
3383
3488
|
|
|
3384
3489
|
// src/extract/infra/shared.ts
|
|
@@ -3416,7 +3521,7 @@ function dependsOnList(value) {
|
|
|
3416
3521
|
}
|
|
3417
3522
|
function serviceNameToServiceNode(name, services) {
|
|
3418
3523
|
for (const s of services) {
|
|
3419
|
-
if (s.node.name === name ||
|
|
3524
|
+
if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
|
|
3420
3525
|
}
|
|
3421
3526
|
return null;
|
|
3422
3527
|
}
|
|
@@ -3425,16 +3530,24 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3425
3530
|
let edgesAdded = 0;
|
|
3426
3531
|
let composePath = null;
|
|
3427
3532
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3428
|
-
const abs =
|
|
3533
|
+
const abs = import_node_path24.default.join(scanPath, name);
|
|
3429
3534
|
if (await exists(abs)) {
|
|
3430
3535
|
composePath = abs;
|
|
3431
3536
|
break;
|
|
3432
3537
|
}
|
|
3433
3538
|
}
|
|
3434
3539
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
3435
|
-
|
|
3540
|
+
let compose;
|
|
3541
|
+
try {
|
|
3542
|
+
compose = await readYaml(composePath);
|
|
3543
|
+
} catch (err) {
|
|
3544
|
+
console.warn(
|
|
3545
|
+
`[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
|
|
3546
|
+
);
|
|
3547
|
+
return { nodesAdded, edgesAdded };
|
|
3548
|
+
}
|
|
3436
3549
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3437
|
-
const evidenceFile =
|
|
3550
|
+
const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
|
|
3438
3551
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3439
3552
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3440
3553
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3475,8 +3588,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3475
3588
|
|
|
3476
3589
|
// src/extract/infra/dockerfile.ts
|
|
3477
3590
|
init_cjs_shims();
|
|
3478
|
-
var
|
|
3479
|
-
var
|
|
3591
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3592
|
+
var import_node_fs13 = require("fs");
|
|
3480
3593
|
var import_types17 = require("@neat.is/types");
|
|
3481
3594
|
function runtimeImage(content) {
|
|
3482
3595
|
const lines = content.split("\n");
|
|
@@ -3496,9 +3609,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3496
3609
|
let nodesAdded = 0;
|
|
3497
3610
|
let edgesAdded = 0;
|
|
3498
3611
|
for (const service of services) {
|
|
3499
|
-
const dockerfilePath =
|
|
3612
|
+
const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
|
|
3500
3613
|
if (!await exists(dockerfilePath)) continue;
|
|
3501
|
-
|
|
3614
|
+
let content;
|
|
3615
|
+
try {
|
|
3616
|
+
content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
|
|
3617
|
+
} catch (err) {
|
|
3618
|
+
console.warn(
|
|
3619
|
+
`[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
|
|
3620
|
+
);
|
|
3621
|
+
continue;
|
|
3622
|
+
}
|
|
3502
3623
|
const image = runtimeImage(content);
|
|
3503
3624
|
if (!image) continue;
|
|
3504
3625
|
const node = makeInfraNode("container-image", image);
|
|
@@ -3515,7 +3636,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3515
3636
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3516
3637
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3517
3638
|
evidence: {
|
|
3518
|
-
file:
|
|
3639
|
+
file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
|
|
3519
3640
|
}
|
|
3520
3641
|
};
|
|
3521
3642
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3527,19 +3648,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3527
3648
|
|
|
3528
3649
|
// src/extract/infra/terraform.ts
|
|
3529
3650
|
init_cjs_shims();
|
|
3530
|
-
var
|
|
3531
|
-
var
|
|
3651
|
+
var import_node_fs14 = require("fs");
|
|
3652
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3532
3653
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3533
3654
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3534
3655
|
if (depth > max) return [];
|
|
3535
3656
|
const out = [];
|
|
3536
|
-
const entries = await
|
|
3657
|
+
const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3537
3658
|
for (const entry of entries) {
|
|
3538
3659
|
if (entry.isDirectory()) {
|
|
3539
3660
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3540
|
-
out.push(...await walkTfFiles(
|
|
3661
|
+
out.push(...await walkTfFiles(import_node_path26.default.join(start, entry.name), depth + 1, max));
|
|
3541
3662
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3542
|
-
out.push(
|
|
3663
|
+
out.push(import_node_path26.default.join(start, entry.name));
|
|
3543
3664
|
}
|
|
3544
3665
|
}
|
|
3545
3666
|
return out;
|
|
@@ -3548,7 +3669,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3548
3669
|
let nodesAdded = 0;
|
|
3549
3670
|
const files = await walkTfFiles(scanPath);
|
|
3550
3671
|
for (const file of files) {
|
|
3551
|
-
const content = await
|
|
3672
|
+
const content = await import_node_fs14.promises.readFile(file, "utf8");
|
|
3552
3673
|
RESOURCE_RE.lastIndex = 0;
|
|
3553
3674
|
let m;
|
|
3554
3675
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3566,8 +3687,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3566
3687
|
|
|
3567
3688
|
// src/extract/infra/k8s.ts
|
|
3568
3689
|
init_cjs_shims();
|
|
3569
|
-
var
|
|
3570
|
-
var
|
|
3690
|
+
var import_node_fs15 = require("fs");
|
|
3691
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3571
3692
|
var import_yaml3 = require("yaml");
|
|
3572
3693
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3573
3694
|
Service: "k8s-service",
|
|
@@ -3581,13 +3702,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3581
3702
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3582
3703
|
if (depth > max) return [];
|
|
3583
3704
|
const out = [];
|
|
3584
|
-
const entries = await
|
|
3705
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3585
3706
|
for (const entry of entries) {
|
|
3586
3707
|
if (entry.isDirectory()) {
|
|
3587
3708
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3588
|
-
out.push(...await walkYamlFiles2(
|
|
3589
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3590
|
-
out.push(
|
|
3709
|
+
out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry.name), depth + 1, max));
|
|
3710
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry.name))) {
|
|
3711
|
+
out.push(import_node_path27.default.join(start, entry.name));
|
|
3591
3712
|
}
|
|
3592
3713
|
}
|
|
3593
3714
|
return out;
|
|
@@ -3596,7 +3717,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3596
3717
|
let nodesAdded = 0;
|
|
3597
3718
|
const files = await walkYamlFiles2(scanPath);
|
|
3598
3719
|
for (const file of files) {
|
|
3599
|
-
const content = await
|
|
3720
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3600
3721
|
let docs;
|
|
3601
3722
|
try {
|
|
3602
3723
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3662,8 +3783,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3662
3783
|
|
|
3663
3784
|
// src/persist.ts
|
|
3664
3785
|
init_cjs_shims();
|
|
3665
|
-
var
|
|
3666
|
-
var
|
|
3786
|
+
var import_node_fs16 = require("fs");
|
|
3787
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3667
3788
|
var SCHEMA_VERSION = 2;
|
|
3668
3789
|
function migrateV1ToV2(payload) {
|
|
3669
3790
|
const nodes = payload.graph.nodes;
|
|
@@ -3677,7 +3798,7 @@ function migrateV1ToV2(payload) {
|
|
|
3677
3798
|
return { ...payload, schemaVersion: 2 };
|
|
3678
3799
|
}
|
|
3679
3800
|
async function ensureDir(filePath) {
|
|
3680
|
-
await
|
|
3801
|
+
await import_node_fs16.promises.mkdir(import_node_path28.default.dirname(filePath), { recursive: true });
|
|
3681
3802
|
}
|
|
3682
3803
|
async function saveGraphToDisk(graph, outPath) {
|
|
3683
3804
|
await ensureDir(outPath);
|
|
@@ -3687,13 +3808,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
3687
3808
|
graph: graph.export()
|
|
3688
3809
|
};
|
|
3689
3810
|
const tmp = `${outPath}.tmp`;
|
|
3690
|
-
await
|
|
3691
|
-
await
|
|
3811
|
+
await import_node_fs16.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
3812
|
+
await import_node_fs16.promises.rename(tmp, outPath);
|
|
3692
3813
|
}
|
|
3693
3814
|
async function loadGraphFromDisk(graph, outPath) {
|
|
3694
3815
|
let raw;
|
|
3695
3816
|
try {
|
|
3696
|
-
raw = await
|
|
3817
|
+
raw = await import_node_fs16.promises.readFile(outPath, "utf8");
|
|
3697
3818
|
} catch (err) {
|
|
3698
3819
|
if (err.code === "ENOENT") return;
|
|
3699
3820
|
throw err;
|
|
@@ -3752,7 +3873,7 @@ var import_types19 = require("@neat.is/types");
|
|
|
3752
3873
|
|
|
3753
3874
|
// src/diff.ts
|
|
3754
3875
|
init_cjs_shims();
|
|
3755
|
-
var
|
|
3876
|
+
var import_node_fs17 = require("fs");
|
|
3756
3877
|
async function loadSnapshotForDiff(target) {
|
|
3757
3878
|
if (/^https?:\/\//i.test(target)) {
|
|
3758
3879
|
const res = await fetch(target);
|
|
@@ -3761,7 +3882,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
3761
3882
|
}
|
|
3762
3883
|
return await res.json();
|
|
3763
3884
|
}
|
|
3764
|
-
const raw = await
|
|
3885
|
+
const raw = await import_node_fs17.promises.readFile(target, "utf8");
|
|
3765
3886
|
return JSON.parse(raw);
|
|
3766
3887
|
}
|
|
3767
3888
|
function indexEntries(entries) {
|
|
@@ -3829,23 +3950,23 @@ function canonicalJson(value) {
|
|
|
3829
3950
|
|
|
3830
3951
|
// src/projects.ts
|
|
3831
3952
|
init_cjs_shims();
|
|
3832
|
-
var
|
|
3953
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
3833
3954
|
function pathsForProject(project, baseDir) {
|
|
3834
3955
|
if (project === DEFAULT_PROJECT) {
|
|
3835
3956
|
return {
|
|
3836
|
-
snapshotPath:
|
|
3837
|
-
errorsPath:
|
|
3838
|
-
staleEventsPath:
|
|
3839
|
-
embeddingsCachePath:
|
|
3840
|
-
policyViolationsPath:
|
|
3957
|
+
snapshotPath: import_node_path29.default.join(baseDir, "graph.json"),
|
|
3958
|
+
errorsPath: import_node_path29.default.join(baseDir, "errors.ndjson"),
|
|
3959
|
+
staleEventsPath: import_node_path29.default.join(baseDir, "stale-events.ndjson"),
|
|
3960
|
+
embeddingsCachePath: import_node_path29.default.join(baseDir, "embeddings.json"),
|
|
3961
|
+
policyViolationsPath: import_node_path29.default.join(baseDir, "policy-violations.ndjson")
|
|
3841
3962
|
};
|
|
3842
3963
|
}
|
|
3843
3964
|
return {
|
|
3844
|
-
snapshotPath:
|
|
3845
|
-
errorsPath:
|
|
3846
|
-
staleEventsPath:
|
|
3847
|
-
embeddingsCachePath:
|
|
3848
|
-
policyViolationsPath:
|
|
3965
|
+
snapshotPath: import_node_path29.default.join(baseDir, `${project}.json`),
|
|
3966
|
+
errorsPath: import_node_path29.default.join(baseDir, `errors.${project}.ndjson`),
|
|
3967
|
+
staleEventsPath: import_node_path29.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
3968
|
+
embeddingsCachePath: import_node_path29.default.join(baseDir, `embeddings.${project}.json`),
|
|
3969
|
+
policyViolationsPath: import_node_path29.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3849
3970
|
};
|
|
3850
3971
|
}
|
|
3851
3972
|
var Projects = class {
|
|
@@ -3881,49 +4002,49 @@ var Projects = class {
|
|
|
3881
4002
|
|
|
3882
4003
|
// src/registry.ts
|
|
3883
4004
|
init_cjs_shims();
|
|
3884
|
-
var
|
|
4005
|
+
var import_node_fs18 = require("fs");
|
|
3885
4006
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
3886
|
-
var
|
|
4007
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
3887
4008
|
var import_types18 = require("@neat.is/types");
|
|
3888
4009
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
3889
4010
|
var LOCK_RETRY_MS = 50;
|
|
3890
4011
|
function neatHome() {
|
|
3891
4012
|
const override = process.env.NEAT_HOME;
|
|
3892
|
-
if (override && override.length > 0) return
|
|
3893
|
-
return
|
|
4013
|
+
if (override && override.length > 0) return import_node_path30.default.resolve(override);
|
|
4014
|
+
return import_node_path30.default.join(import_node_os2.default.homedir(), ".neat");
|
|
3894
4015
|
}
|
|
3895
4016
|
function registryPath() {
|
|
3896
|
-
return
|
|
4017
|
+
return import_node_path30.default.join(neatHome(), "projects.json");
|
|
3897
4018
|
}
|
|
3898
4019
|
function registryLockPath() {
|
|
3899
|
-
return
|
|
4020
|
+
return import_node_path30.default.join(neatHome(), "projects.json.lock");
|
|
3900
4021
|
}
|
|
3901
4022
|
async function normalizeProjectPath(input) {
|
|
3902
|
-
const resolved =
|
|
4023
|
+
const resolved = import_node_path30.default.resolve(input);
|
|
3903
4024
|
try {
|
|
3904
|
-
return await
|
|
4025
|
+
return await import_node_fs18.promises.realpath(resolved);
|
|
3905
4026
|
} catch {
|
|
3906
4027
|
return resolved;
|
|
3907
4028
|
}
|
|
3908
4029
|
}
|
|
3909
4030
|
async function writeAtomically(target, contents) {
|
|
3910
|
-
await
|
|
4031
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(target), { recursive: true });
|
|
3911
4032
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
3912
|
-
const fd = await
|
|
4033
|
+
const fd = await import_node_fs18.promises.open(tmp, "w");
|
|
3913
4034
|
try {
|
|
3914
4035
|
await fd.writeFile(contents, "utf8");
|
|
3915
4036
|
await fd.sync();
|
|
3916
4037
|
} finally {
|
|
3917
4038
|
await fd.close();
|
|
3918
4039
|
}
|
|
3919
|
-
await
|
|
4040
|
+
await import_node_fs18.promises.rename(tmp, target);
|
|
3920
4041
|
}
|
|
3921
4042
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
3922
4043
|
const deadline = Date.now() + timeoutMs;
|
|
3923
|
-
await
|
|
4044
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(lockPath), { recursive: true });
|
|
3924
4045
|
while (true) {
|
|
3925
4046
|
try {
|
|
3926
|
-
const fd = await
|
|
4047
|
+
const fd = await import_node_fs18.promises.open(lockPath, "wx");
|
|
3927
4048
|
await fd.close();
|
|
3928
4049
|
return;
|
|
3929
4050
|
} catch (err) {
|
|
@@ -3939,7 +4060,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
3939
4060
|
}
|
|
3940
4061
|
}
|
|
3941
4062
|
async function releaseLock(lockPath) {
|
|
3942
|
-
await
|
|
4063
|
+
await import_node_fs18.promises.unlink(lockPath).catch(() => {
|
|
3943
4064
|
});
|
|
3944
4065
|
}
|
|
3945
4066
|
async function withLock(fn) {
|
|
@@ -3955,7 +4076,7 @@ async function readRegistry() {
|
|
|
3955
4076
|
const file = registryPath();
|
|
3956
4077
|
let raw;
|
|
3957
4078
|
try {
|
|
3958
|
-
raw = await
|
|
4079
|
+
raw = await import_node_fs18.promises.readFile(file, "utf8");
|
|
3959
4080
|
} catch (err) {
|
|
3960
4081
|
if (err.code === "ENOENT") {
|
|
3961
4082
|
return { version: 1, projects: [] };
|
|
@@ -4472,14 +4593,14 @@ init_otel_grpc();
|
|
|
4472
4593
|
|
|
4473
4594
|
// src/daemon.ts
|
|
4474
4595
|
init_cjs_shims();
|
|
4475
|
-
var
|
|
4476
|
-
var
|
|
4596
|
+
var import_node_fs19 = require("fs");
|
|
4597
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4477
4598
|
function neatHomeFor(opts) {
|
|
4478
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
4599
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path33.default.resolve(opts.neatHome);
|
|
4479
4600
|
const env = process.env.NEAT_HOME;
|
|
4480
|
-
if (env && env.length > 0) return
|
|
4601
|
+
if (env && env.length > 0) return import_node_path33.default.resolve(env);
|
|
4481
4602
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4482
|
-
return
|
|
4603
|
+
return import_node_path33.default.join(home, ".neat");
|
|
4483
4604
|
}
|
|
4484
4605
|
function routeSpanToProject(serviceName, projects) {
|
|
4485
4606
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -4493,7 +4614,7 @@ function routeSpanToProject(serviceName, projects) {
|
|
|
4493
4614
|
}
|
|
4494
4615
|
async function bootstrapProject(entry) {
|
|
4495
4616
|
try {
|
|
4496
|
-
const stat = await
|
|
4617
|
+
const stat = await import_node_fs19.promises.stat(entry.path);
|
|
4497
4618
|
if (!stat.isDirectory()) {
|
|
4498
4619
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
4499
4620
|
}
|
|
@@ -4516,7 +4637,7 @@ async function bootstrapProject(entry) {
|
|
|
4516
4637
|
const graph = getGraph(entry.name);
|
|
4517
4638
|
const outPath = pathsForProject(
|
|
4518
4639
|
entry.name,
|
|
4519
|
-
|
|
4640
|
+
import_node_path33.default.join(entry.path, "neat-out")
|
|
4520
4641
|
).snapshotPath;
|
|
4521
4642
|
await loadGraphFromDisk(graph, outPath);
|
|
4522
4643
|
await extractFromDirectory(graph, entry.path);
|
|
@@ -4535,13 +4656,13 @@ async function startDaemon(opts = {}) {
|
|
|
4535
4656
|
const home = neatHomeFor(opts);
|
|
4536
4657
|
const regPath = registryPath();
|
|
4537
4658
|
try {
|
|
4538
|
-
await
|
|
4659
|
+
await import_node_fs19.promises.access(regPath);
|
|
4539
4660
|
} catch {
|
|
4540
4661
|
throw new Error(
|
|
4541
4662
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
4542
4663
|
);
|
|
4543
4664
|
}
|
|
4544
|
-
const pidPath =
|
|
4665
|
+
const pidPath = import_node_path33.default.join(home, "neatd.pid");
|
|
4545
4666
|
await writeAtomically(pidPath, `${process.pid}
|
|
4546
4667
|
`);
|
|
4547
4668
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -4607,7 +4728,7 @@ async function startDaemon(opts = {}) {
|
|
|
4607
4728
|
} catch {
|
|
4608
4729
|
}
|
|
4609
4730
|
}
|
|
4610
|
-
await
|
|
4731
|
+
await import_node_fs19.promises.unlink(pidPath).catch(() => {
|
|
4611
4732
|
});
|
|
4612
4733
|
};
|
|
4613
4734
|
return { slots, reload, stop, pidPath };
|