@neat.is/core 0.4.31-dev.20260718 → 0.5.0

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/neatd.js CHANGED
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  reconcileDaemonRecordSync,
4
4
  startDaemon
5
- } from "./chunk-2AJHEZIB.js";
5
+ } from "./chunk-5MGHWP6R.js";
6
6
  import {
7
7
  listProjects,
8
8
  registryPath
9
- } from "./chunk-4M2YMGUM.js";
9
+ } from "./chunk-QQDZSIY3.js";
10
10
  import {
11
11
  BindAuthorityError,
12
12
  __require
package/dist/server.cjs CHANGED
@@ -1445,7 +1445,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1445
1445
  for (const id of walk3.path) {
1446
1446
  const owner = resolveOwningService(graph, id);
1447
1447
  if (!owner) continue;
1448
- const { id: serviceId4, svc } = owner;
1448
+ const { id: serviceId5, svc } = owner;
1449
1449
  const deps = svc.dependencies ?? {};
1450
1450
  for (const pair of candidatePairs) {
1451
1451
  const declared = deps[pair.driver];
@@ -1458,7 +1458,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1458
1458
  );
1459
1459
  if (!result.compatible) {
1460
1460
  return {
1461
- rootCauseNode: serviceId4,
1461
+ rootCauseNode: serviceId5,
1462
1462
  rootCauseReason: result.reason ?? "incompatible driver",
1463
1463
  ...result.minDriverVersion ? {
1464
1464
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1473,7 +1473,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1473
1473
  for (const id of walk3.path) {
1474
1474
  const owner = resolveOwningService(graph, id);
1475
1475
  if (!owner) continue;
1476
- const { id: serviceId4, svc } = owner;
1476
+ const { id: serviceId5, svc } = owner;
1477
1477
  const deps = svc.dependencies ?? {};
1478
1478
  const serviceNodeEngine = svc.nodeEngine;
1479
1479
  for (const constraint of nodeEngineConstraints()) {
@@ -1482,7 +1482,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1482
1482
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1483
1483
  if (!result.compatible && result.reason) {
1484
1484
  return {
1485
- rootCauseNode: serviceId4,
1485
+ rootCauseNode: serviceId5,
1486
1486
  rootCauseReason: result.reason,
1487
1487
  ...result.requiredNodeVersion ? {
1488
1488
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1497,7 +1497,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1497
1497
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1498
1498
  if (!result.compatible && result.reason) {
1499
1499
  return {
1500
- rootCauseNode: serviceId4,
1500
+ rootCauseNode: serviceId5,
1501
1501
  rootCauseReason: result.reason,
1502
1502
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1503
1503
  };
@@ -1588,9 +1588,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1588
1588
  function isFailingCallEdge(e) {
1589
1589
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1590
1590
  }
1591
- function callSourcesForService(graph, serviceId4) {
1592
- const ids = [serviceId4];
1593
- for (const edgeId of graph.outboundEdges(serviceId4)) {
1591
+ function callSourcesForService(graph, serviceId5) {
1592
+ const ids = [serviceId5];
1593
+ for (const edgeId of graph.outboundEdges(serviceId5)) {
1594
1594
  const e = graph.getEdgeAttributes(edgeId);
1595
1595
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1596
1596
  const tgt = graph.getNodeAttributes(e.target);
@@ -1607,9 +1607,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1607
1607
  }
1608
1608
  return id < curId;
1609
1609
  }
1610
- function dominantFailingCall(graph, serviceId4, visited) {
1610
+ function dominantFailingCall(graph, serviceId5, visited) {
1611
1611
  let best = null;
1612
- for (const src of callSourcesForService(graph, serviceId4)) {
1612
+ for (const src of callSourcesForService(graph, serviceId5)) {
1613
1613
  for (const edgeId of graph.outboundEdges(src)) {
1614
1614
  const e = graph.getEdgeAttributes(edgeId);
1615
1615
  if (!isFailingCallEdge(e)) continue;
@@ -1820,14 +1820,23 @@ function getObservedDependencies(graph, nodeId) {
1820
1820
  function bucketKey(source, target, type) {
1821
1821
  return `${type}|${source}|${target}`;
1822
1822
  }
1823
+ function bucketSourceFor(graph, edge) {
1824
+ if (edge.type !== import_types2.EdgeType.CONNECTS_TO) return edge.source;
1825
+ const parsed = (0, import_types2.parseFileId)(edge.source);
1826
+ if (!parsed || !graph.hasNode(edge.target)) return edge.source;
1827
+ const target = graph.getNodeAttributes(edge.target);
1828
+ if (target.type !== import_types2.NodeType.DatabaseNode) return edge.source;
1829
+ return (0, import_types2.serviceId)(parsed.service);
1830
+ }
1823
1831
  function bucketEdges(graph) {
1824
1832
  const buckets = /* @__PURE__ */ new Map();
1825
1833
  graph.forEachEdge((id, attrs) => {
1826
1834
  const e = attrs;
1827
1835
  const parsed = (0, import_types2.parseEdgeId)(id);
1828
1836
  const provenance = parsed?.provenance ?? e.provenance;
1829
- const key = bucketKey(e.source, e.target, e.type);
1830
- const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
1837
+ const source = bucketSourceFor(graph, e);
1838
+ const key = bucketKey(source, e.target, e.type);
1839
+ const cur = buckets.get(key) ?? { source, target: e.target, type: e.type };
1831
1840
  switch (provenance) {
1832
1841
  case import_types2.Provenance.EXTRACTED:
1833
1842
  cur.extracted = e;
@@ -3116,6 +3125,27 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
3116
3125
  graph.addNode(id, node);
3117
3126
  return id;
3118
3127
  }
3128
+ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
3129
+ if (!graph.hasNode(serviceNodeId)) return null;
3130
+ const sources = [serviceNodeId];
3131
+ for (const edgeId of graph.outboundEdges(serviceNodeId)) {
3132
+ const e = graph.getEdgeAttributes(edgeId);
3133
+ if (e.type === import_types4.EdgeType.CONTAINS) sources.push(e.target);
3134
+ }
3135
+ const matches = /* @__PURE__ */ new Set();
3136
+ for (const src of sources) {
3137
+ if (!graph.hasNode(src)) continue;
3138
+ for (const edgeId of graph.outboundEdges(src)) {
3139
+ const edge = graph.getEdgeAttributes(edgeId);
3140
+ if (edge.type !== import_types4.EdgeType.CONNECTS_TO || edge.provenance !== import_types4.Provenance.EXTRACTED) continue;
3141
+ if (!graph.hasNode(edge.target)) continue;
3142
+ const target = graph.getNodeAttributes(edge.target);
3143
+ if (target.type !== import_types4.NodeType.DatabaseNode || target.engine !== engine) continue;
3144
+ matches.add(edge.target);
3145
+ }
3146
+ }
3147
+ return matches.size === 1 ? [...matches][0] : null;
3148
+ }
3119
3149
  function ensureFrontierNode(graph, host, ts) {
3120
3150
  const id = frontierIdFor(host);
3121
3151
  if (graph.hasNode(id)) {
@@ -3349,13 +3379,18 @@ async function handleSpan(ctx, span) {
3349
3379
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
3350
3380
  targetId = (0, import_types4.databaseId)(host);
3351
3381
  } else {
3352
- const localName = span.dbName ?? span.dbSystem;
3353
- targetId = ensureLocalDatabaseNode(
3354
- ctx.graph,
3355
- span.service,
3356
- localName,
3357
- span.dbSystem
3358
- );
3382
+ const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
3383
+ if (declared) {
3384
+ targetId = declared;
3385
+ } else {
3386
+ const localName = span.dbName ?? span.dbSystem;
3387
+ targetId = ensureLocalDatabaseNode(
3388
+ ctx.graph,
3389
+ span.service,
3390
+ localName,
3391
+ span.dbSystem
3392
+ );
3393
+ }
3359
3394
  }
3360
3395
  const result = upsertObservedEdge(
3361
3396
  ctx.graph,
@@ -3539,29 +3574,29 @@ function promoteFrontierNodes(graph, opts = {}) {
3539
3574
  toPromote.push({ frontierId: id, serviceId: target });
3540
3575
  });
3541
3576
  let promoted = 0;
3542
- for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
3577
+ for (const { frontierId: frontierId2, serviceId: serviceId5 } of toPromote) {
3543
3578
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
3544
3579
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
3545
3580
  if (!gate.allowed) {
3546
3581
  continue;
3547
3582
  }
3548
3583
  }
3549
- rewireFrontierEdges(graph, frontierId2, serviceId4);
3584
+ rewireFrontierEdges(graph, frontierId2, serviceId5);
3550
3585
  graph.dropNode(frontierId2);
3551
3586
  promoted++;
3552
3587
  }
3553
3588
  return promoted;
3554
3589
  }
3555
- function rewireFrontierEdges(graph, frontierId2, serviceId4) {
3590
+ function rewireFrontierEdges(graph, frontierId2, serviceId5) {
3556
3591
  const inbound = [...graph.inboundEdges(frontierId2)];
3557
3592
  const outbound = [...graph.outboundEdges(frontierId2)];
3558
3593
  for (const edgeId of inbound) {
3559
3594
  const edge = graph.getEdgeAttributes(edgeId);
3560
- rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
3595
+ rebuildEdge(graph, edge, edge.source, serviceId5, edgeId);
3561
3596
  }
3562
3597
  for (const edgeId of outbound) {
3563
3598
  const edge = graph.getEdgeAttributes(edgeId);
3564
- rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
3599
+ rebuildEdge(graph, edge, serviceId5, edge.target, edgeId);
3565
3600
  }
3566
3601
  }
3567
3602
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -4357,9 +4392,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
4357
4392
  "StatefulSet",
4358
4393
  "DaemonSet"
4359
4394
  ]);
4360
- function addAliases(graph, serviceId4, candidates) {
4361
- if (!graph.hasNode(serviceId4)) return;
4362
- const node = graph.getNodeAttributes(serviceId4);
4395
+ function addAliases(graph, serviceId5, candidates) {
4396
+ if (!graph.hasNode(serviceId5)) return;
4397
+ const node = graph.getNodeAttributes(serviceId5);
4363
4398
  if (node.type !== import_types7.NodeType.ServiceNode) return;
4364
4399
  const set = new Set(node.aliases ?? []);
4365
4400
  for (const c of candidates) {
@@ -4369,7 +4404,7 @@ function addAliases(graph, serviceId4, candidates) {
4369
4404
  }
4370
4405
  if (set.size === 0) return;
4371
4406
  const updated = { ...node, aliases: [...set].sort() };
4372
- graph.replaceNodeAttributes(serviceId4, updated);
4407
+ graph.replaceNodeAttributes(serviceId5, updated);
4373
4408
  }
4374
4409
  function indexServicesByName(services) {
4375
4410
  const map = /* @__PURE__ */ new Map();
@@ -4402,12 +4437,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
4402
4437
  }
4403
4438
  if (!compose?.services) return;
4404
4439
  for (const [composeName, svc] of Object.entries(compose.services)) {
4405
- const serviceId4 = serviceIndex.get(composeName);
4406
- if (!serviceId4) continue;
4440
+ const serviceId5 = serviceIndex.get(composeName);
4441
+ if (!serviceId5) continue;
4407
4442
  const aliases = /* @__PURE__ */ new Set([composeName]);
4408
4443
  if (svc.container_name) aliases.add(svc.container_name);
4409
4444
  if (svc.hostname) aliases.add(svc.hostname);
4410
- addAliases(graph, serviceId4, aliases);
4445
+ addAliases(graph, serviceId5, aliases);
4411
4446
  }
4412
4447
  }
4413
4448
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -5057,6 +5092,29 @@ async function readIfExists(filePath) {
5057
5092
  return null;
5058
5093
  }
5059
5094
  }
5095
+ async function resolveEnvVar(serviceDir, name) {
5096
+ const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
5097
+ const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
5098
+ const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
5099
+ envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
5100
+ for (const fileName of envNames) {
5101
+ const content = await readIfExists(import_node_path16.default.join(serviceDir, fileName));
5102
+ if (!content) continue;
5103
+ for (const line of content.split("\n")) {
5104
+ const trimmed = line.trim();
5105
+ if (!trimmed || trimmed.startsWith("#")) continue;
5106
+ const eq = trimmed.indexOf("=");
5107
+ if (eq < 0) continue;
5108
+ if (trimmed.slice(0, eq).trim() !== name) continue;
5109
+ let value = trimmed.slice(eq + 1).trim();
5110
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
5111
+ value = value.slice(1, -1);
5112
+ }
5113
+ return value || null;
5114
+ }
5115
+ }
5116
+ return null;
5117
+ }
5060
5118
  async function findFirst(serviceDir, candidates) {
5061
5119
  for (const rel of candidates) {
5062
5120
  const abs = import_node_path16.default.join(serviceDir, rel);
@@ -5154,6 +5212,14 @@ async function parse3(serviceDir) {
5154
5212
  const config = parseConnectionString(urlMatch[1]);
5155
5213
  if (config) return [{ ...config, sourceFile: schemaPath }];
5156
5214
  }
5215
+ const envMatch = body.match(/url\s*=\s*env\(\s*"([^"]+)"\s*\)/);
5216
+ if (envMatch) {
5217
+ const resolved = await resolveEnvVar(serviceDir, envMatch[1]);
5218
+ if (resolved) {
5219
+ const config = parseConnectionString(resolved);
5220
+ if (config) return [{ ...config, sourceFile: schemaPath }];
5221
+ }
5222
+ }
5157
5223
  return [
5158
5224
  {
5159
5225
  host: `${engine}-prisma`,