@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/index.cjs CHANGED
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req) {
100
100
  };
101
101
  }
102
102
  function resolveProtoRoot() {
103
- const here = import_node_path30.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
104
- return import_node_path30.default.resolve(here, "..", "proto");
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, import_node_path30, grpc, protoLoader;
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
- import_node_path30 = __toESM(require("path"), 1);
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 = import_node_path31.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
263
- const protoRoot = import_node_path31.default.resolve(here, "..", "proto");
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) => import_node_path31.default.resolve(protoRoot, 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 import_node_path31, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
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
- import_node_path31 = __toESM(require("path"), 1);
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, path33, edges) {
880
- if (path33.length > best.path.length) {
881
- best = { path: [...path33], edges: [...edges] };
879
+ function step(node, path34, edges) {
880
+ if (path34.length > best.path.length) {
881
+ best = { path: [...path34], edges: [...edges] };
882
882
  }
883
- if (path33.length - 1 >= maxDepth) return;
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
- path33.push(srcId);
888
+ path34.push(srcId);
889
889
  edges.push(edge);
890
- step(srcId, path33, edges);
891
- path33.pop();
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 import_node_fs6 = require("fs");
1906
- var import_node_path6 = __toESM(require("path"), 1);
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 import_minimatch = require("minimatch");
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 = import_node_path6.default.join(scanPath, ".gitignore");
2104
+ const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
2038
2105
  if (!await exists(gitignorePath)) return null;
2039
- const raw = await import_node_fs6.promises.readFile(gitignorePath, "utf8");
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 import_node_fs6.promises.readdir(current, { withFileTypes: true }).catch(() => []);
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 = import_node_path6.default.join(current, entry.name);
2116
+ const child = import_node_path7.default.join(current, entry.name);
2050
2117
  if (options.ig) {
2051
- const rel = import_node_path6.default.relative(scanPath, child).split(import_node_path6.default.sep).join("/");
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 = import_node_path6.default.join(scanPath, pattern);
2067
- if (await exists(import_node_path6.default.join(candidate, "package.json"))) found.add(candidate);
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 = import_node_path6.default.join(scanPath, ...staticSegments);
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 = import_node_path6.default.relative(scanPath, dir).split(import_node_path6.default.sep).join("/");
2082
- if ((0, import_minimatch.minimatch)(rel, pattern) && await exists(import_node_path6.default.join(dir, "package.json"))) {
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 = import_node_path6.default.join(dir, "package.json");
2157
+ const pkgPath = import_node_path7.default.join(dir, "package.json");
2091
2158
  if (!await exists(pkgPath)) return null;
2092
- const pkg = await readJson(pkgPath);
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: import_node_path6.default.relative(scanPath, dir),
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: import_node_path6.default.relative(scanPath, dir)
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 = import_node_path6.default.join(scanPath, "package.json");
2123
- const rootPkg = await exists(rootPkgPath) ? await readJson(rootPkgPath) : null;
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(import_node_path6.default.join(dir, "package.json"))) {
2220
+ if (await exists(import_node_path7.default.join(dir, "package.json"))) {
2137
2221
  candidateDirs.push(dir);
2138
- } else if (await exists(import_node_path6.default.join(dir, "pyproject.toml")) || await exists(import_node_path6.default.join(dir, "requirements.txt")) || await exists(import_node_path6.default.join(dir, "setup.py"))) {
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 = import_node_path6.default.relative(scanPath, existingDir) || ".";
2153
- const b = import_node_path6.default.relative(scanPath, dir) || ".";
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 import_node_path7 = __toESM(require("path"), 1);
2186
- var import_node_fs7 = require("fs");
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(import_node_path7.default.basename(s.dir), s.node.id);
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 = import_node_path7.default.join(scanPath, name);
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
- const compose = await readYaml(composePath);
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 = import_node_path7.default.join(service.dir, "Dockerfile");
2363
+ const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
2267
2364
  if (!await exists(dockerfilePath)) continue;
2268
- const content = await import_node_fs7.promises.readFile(dockerfilePath, "utf8");
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 import_node_fs7.promises.readdir(start, { withFileTypes: true }).catch(() => []);
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(import_node_path7.default.join(start, entry.name), depth + 1, max));
2281
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path7.default.extname(entry.name))) {
2282
- out.push(import_node_path7.default.join(start, entry.name));
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 import_node_fs7.promises.readFile(file, "utf8");
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 import_node_path15 = __toESM(require("path"), 1);
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 import_node_path8 = __toESM(require("path"), 1);
2444
+ var import_node_path9 = __toESM(require("path"), 1);
2340
2445
  async function parse(serviceDir) {
2341
- const yamlPath = import_node_path8.default.join(serviceDir, "db-config.yaml");
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 import_node_fs9 = require("fs");
2360
- var import_node_path10 = __toESM(require("path"), 1);
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 import_node_fs8 = require("fs");
2365
- var import_node_path9 = __toESM(require("path"), 1);
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 import_node_fs8.promises.readFile(filePath, "utf8");
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 = import_node_path9.default.join(serviceDir, rel);
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 import_node_fs9.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
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 = import_node_path10.default.join(serviceDir, entry.name);
2470
- const content = await import_node_fs9.promises.readFile(filePath, "utf8");
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 import_node_path11 = __toESM(require("path"), 1);
2594
+ var import_node_path12 = __toESM(require("path"), 1);
2490
2595
  async function parse3(serviceDir) {
2491
- const schemaPath = import_node_path11.default.join(serviceDir, "prisma", "schema.prisma");
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 import_node_path12 = __toESM(require("path"), 1);
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 = import_node_path12.default.join(serviceDir, candidate);
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 import_node_path13 = __toESM(require("path"), 1);
2791
+ var import_node_path14 = __toESM(require("path"), 1);
2687
2792
  async function parse8(serviceDir) {
2688
- const configPath = import_node_path13.default.join(serviceDir, "config", "config.json");
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 import_node_path14 = __toESM(require("path"), 1);
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 = import_node_path14.default.join(serviceDir, name);
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: import_node_path15.default.relative(scanPath, config.sourceFile).split(import_node_path15.default.sep).join("/")
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 import_node_fs10 = require("fs");
2951
- var import_node_path16 = __toESM(require("path"), 1);
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 import_node_fs10.promises.readdir(current, { withFileTypes: true });
3061
+ const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
2957
3062
  for (const entry of entries) {
2958
- const full = import_node_path16.default.join(current, entry.name);
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 = import_node_path16.default.relative(scanPath, file);
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: import_node_path16.default.basename(file),
3084
+ name: import_node_path17.default.basename(file),
2980
3085
  path: relPath,
2981
- fileType: isConfigFile(import_node_path16.default.basename(file)).fileType
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(import_node_path16.default.sep).join("/") }
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 import_node_path18 = __toESM(require("path"), 1);
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 import_node_fs11 = require("fs");
3019
- var import_node_path17 = __toESM(require("path"), 1);
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 import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3128
+ const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3024
3129
  for (const entry of entries) {
3025
- const full = import_node_path17.default.join(current, entry.name);
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(import_node_path17.default.extname(entry.name))) {
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 import_node_fs11.promises.readFile(p, "utf8");
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(import_node_path18.default.basename(service.dir));
3202
+ knownHosts.add(import_node_path19.default.basename(service.dir));
3098
3203
  knownHosts.add(service.pkg.name);
3099
- hostToNodeId.set(import_node_path18.default.basename(service.dir), service.node.id);
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 = import_node_path18.default.extname(file.path) === ".py" ? pyParser : jsParser;
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: import_node_path18.default.relative(service.dir, evidenceFile.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 import_node_path19 = __toESM(require("path"), 1);
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: import_node_path19.default.relative(serviceDir, file.path),
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 import_node_path20 = __toESM(require("path"), 1);
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: import_node_path20.default.relative(serviceDir, file.path),
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 import_node_path21 = __toESM(require("path"), 1);
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: import_node_path21.default.relative(serviceDir, file.path),
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 import_node_path22 = __toESM(require("path"), 1);
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: import_node_path22.default.relative(serviceDir, file.path),
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 import_node_path23 = __toESM(require("path"), 1);
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 || import_node_path23.default.basename(s.dir) === name) return s.node.id;
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 = import_node_path23.default.join(scanPath, name);
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
- const compose = await readYaml(composePath);
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 = import_node_path23.default.relative(scanPath, composePath).split(import_node_path23.default.sep).join("/");
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 import_node_path24 = __toESM(require("path"), 1);
3479
- var import_node_fs12 = require("fs");
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 = import_node_path24.default.join(service.dir, "Dockerfile");
3612
+ const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
3500
3613
  if (!await exists(dockerfilePath)) continue;
3501
- const content = await import_node_fs12.promises.readFile(dockerfilePath, "utf8");
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: import_node_path24.default.relative(scanPath, dockerfilePath).split(import_node_path24.default.sep).join("/")
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 import_node_fs13 = require("fs");
3531
- var import_node_path25 = __toESM(require("path"), 1);
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 import_node_fs13.promises.readdir(start, { withFileTypes: true }).catch(() => []);
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(import_node_path25.default.join(start, entry.name), depth + 1, max));
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(import_node_path25.default.join(start, entry.name));
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 import_node_fs13.promises.readFile(file, "utf8");
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 import_node_fs14 = require("fs");
3570
- var import_node_path26 = __toESM(require("path"), 1);
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 import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
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(import_node_path26.default.join(start, entry.name), depth + 1, max));
3589
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path26.default.extname(entry.name))) {
3590
- out.push(import_node_path26.default.join(start, entry.name));
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 import_node_fs14.promises.readFile(file, "utf8");
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 import_node_fs15 = require("fs");
3666
- var import_node_path27 = __toESM(require("path"), 1);
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 import_node_fs15.promises.mkdir(import_node_path27.default.dirname(filePath), { recursive: true });
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 import_node_fs15.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
3691
- await import_node_fs15.promises.rename(tmp, outPath);
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 import_node_fs15.promises.readFile(outPath, "utf8");
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 import_node_fs16 = require("fs");
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 import_node_fs16.promises.readFile(target, "utf8");
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 import_node_path28 = __toESM(require("path"), 1);
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: import_node_path28.default.join(baseDir, "graph.json"),
3837
- errorsPath: import_node_path28.default.join(baseDir, "errors.ndjson"),
3838
- staleEventsPath: import_node_path28.default.join(baseDir, "stale-events.ndjson"),
3839
- embeddingsCachePath: import_node_path28.default.join(baseDir, "embeddings.json"),
3840
- policyViolationsPath: import_node_path28.default.join(baseDir, "policy-violations.ndjson")
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: import_node_path28.default.join(baseDir, `${project}.json`),
3845
- errorsPath: import_node_path28.default.join(baseDir, `errors.${project}.ndjson`),
3846
- staleEventsPath: import_node_path28.default.join(baseDir, `stale-events.${project}.ndjson`),
3847
- embeddingsCachePath: import_node_path28.default.join(baseDir, `embeddings.${project}.json`),
3848
- policyViolationsPath: import_node_path28.default.join(baseDir, `policy-violations.${project}.ndjson`)
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 import_node_fs17 = require("fs");
4005
+ var import_node_fs18 = require("fs");
3885
4006
  var import_node_os2 = __toESM(require("os"), 1);
3886
- var import_node_path29 = __toESM(require("path"), 1);
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 import_node_path29.default.resolve(override);
3893
- return import_node_path29.default.join(import_node_os2.default.homedir(), ".neat");
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 import_node_path29.default.join(neatHome(), "projects.json");
4017
+ return import_node_path30.default.join(neatHome(), "projects.json");
3897
4018
  }
3898
4019
  function registryLockPath() {
3899
- return import_node_path29.default.join(neatHome(), "projects.json.lock");
4020
+ return import_node_path30.default.join(neatHome(), "projects.json.lock");
3900
4021
  }
3901
4022
  async function normalizeProjectPath(input) {
3902
- const resolved = import_node_path29.default.resolve(input);
4023
+ const resolved = import_node_path30.default.resolve(input);
3903
4024
  try {
3904
- return await import_node_fs17.promises.realpath(resolved);
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 import_node_fs17.promises.mkdir(import_node_path29.default.dirname(target), { recursive: true });
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 import_node_fs17.promises.open(tmp, "w");
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 import_node_fs17.promises.rename(tmp, target);
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 import_node_fs17.promises.mkdir(import_node_path29.default.dirname(lockPath), { recursive: true });
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 import_node_fs17.promises.open(lockPath, "wx");
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 import_node_fs17.promises.unlink(lockPath).catch(() => {
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 import_node_fs17.promises.readFile(file, "utf8");
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 import_node_fs18 = require("fs");
4476
- var import_node_path32 = __toESM(require("path"), 1);
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 import_node_path32.default.resolve(opts.neatHome);
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 import_node_path32.default.resolve(env);
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 import_node_path32.default.join(home, ".neat");
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 import_node_fs18.promises.stat(entry.path);
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
- import_node_path32.default.join(entry.path, "neat-out")
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 import_node_fs18.promises.access(regPath);
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 = import_node_path32.default.join(home, "neatd.pid");
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 import_node_fs18.promises.unlink(pidPath).catch(() => {
4731
+ await import_node_fs19.promises.unlink(pidPath).catch(() => {
4611
4732
  });
4612
4733
  };
4613
4734
  return { slots, reload, stop, pidPath };