@neat.is/core 0.2.7 → 0.2.9

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