@neat.is/core 0.3.4 → 0.3.6

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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  routeSpanToProject,
3
3
  startDaemon
4
- } from "./chunk-T3A2GK3X.js";
4
+ } from "./chunk-YHQYHFI3.js";
5
5
  import {
6
6
  ProjectNameCollisionError,
7
7
  addProject,
@@ -37,7 +37,7 @@ import {
37
37
  thresholdForEdgeType,
38
38
  touchLastSeen,
39
39
  writeAtomically
40
- } from "./chunk-33ZZ2ZID.js";
40
+ } from "./chunk-ZU2RQRCN.js";
41
41
  import {
42
42
  startOtelGrpcReceiver
43
43
  } from "./chunk-G3PDTGOW.js";
package/dist/neatd.cjs CHANGED
@@ -371,6 +371,7 @@ var init_otel = __esm({
371
371
  init_cjs_shims();
372
372
  var import_node_fs22 = require("fs");
373
373
  var import_node_path38 = __toESM(require("path"), 1);
374
+ var import_node_module = require("module");
374
375
 
375
376
  // src/daemon.ts
376
377
  init_cjs_shims();
@@ -749,11 +750,16 @@ init_cjs_shims();
749
750
  var import_types = require("@neat.is/types");
750
751
  var ROOT_CAUSE_MAX_DEPTH = 5;
751
752
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
753
+ function isFrontierNode(graph, nodeId) {
754
+ if (!graph.hasNode(nodeId)) return false;
755
+ const attrs = graph.getNodeAttributes(nodeId);
756
+ return attrs.type === import_types.NodeType.FrontierNode;
757
+ }
752
758
  function bestEdgeBySource(graph, edgeIds) {
753
759
  const best = /* @__PURE__ */ new Map();
754
760
  for (const id of edgeIds) {
755
761
  const e = graph.getEdgeAttributes(id);
756
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
762
+ if (isFrontierNode(graph, e.source)) continue;
757
763
  const cur = best.get(e.source);
758
764
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
759
765
  best.set(e.source, e);
@@ -765,7 +771,7 @@ function bestEdgeByTarget(graph, edgeIds) {
765
771
  const best = /* @__PURE__ */ new Map();
766
772
  for (const id of edgeIds) {
767
773
  const e = graph.getEdgeAttributes(id);
768
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
774
+ if (isFrontierNode(graph, e.target)) continue;
769
775
  const cur = best.get(e.target);
770
776
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
771
777
  best.set(e.target, e);
@@ -777,8 +783,7 @@ var PROVENANCE_CEILING = {
777
783
  OBSERVED: 1,
778
784
  INFERRED: 0.7,
779
785
  EXTRACTED: 0.5,
780
- STALE: 0.3,
781
- FRONTIER: 0.3
786
+ STALE: 0.3
782
787
  };
783
788
  function volumeWeight(spanCount) {
784
789
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -1062,8 +1067,8 @@ var evaluateStructural = ({
1062
1067
  for (const edgeId of graph.outboundEdges(id)) {
1063
1068
  const e = graph.getEdgeAttributes(edgeId);
1064
1069
  if (e.type !== rule.edgeType) continue;
1065
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1066
1070
  const target = graph.getNodeAttributes(e.target);
1071
+ if (target.type === import_types2.NodeType.FrontierNode) continue;
1067
1072
  if (target.type === rule.toNodeType) {
1068
1073
  satisfied = true;
1069
1074
  break;
@@ -1182,8 +1187,8 @@ var evaluateCompatibility = ({
1182
1187
  for (const edgeId of graph.outboundEdges(svcId)) {
1183
1188
  const e = graph.getEdgeAttributes(edgeId);
1184
1189
  if (e.type !== import_types2.EdgeType.CONNECTS_TO) continue;
1185
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1186
1190
  const dbAttrs = graph.getNodeAttributes(e.target);
1191
+ if (dbAttrs.type === import_types2.NodeType.FrontierNode) continue;
1187
1192
  if (dbAttrs.type !== import_types2.NodeType.DatabaseNode) continue;
1188
1193
  const db = dbAttrs;
1189
1194
  for (const pair of compatPairs()) {
@@ -1478,31 +1483,6 @@ function ensureFrontierNode(graph, host, ts) {
1478
1483
  graph.addNode(id, node);
1479
1484
  return id;
1480
1485
  }
1481
- function upsertFrontierEdge(graph, type, source, target, ts) {
1482
- const id = (0, import_types3.frontierEdgeId)(source, target, type);
1483
- if (graph.hasEdge(id)) {
1484
- const existing = graph.getEdgeAttributes(id);
1485
- const updated = {
1486
- ...existing,
1487
- provenance: import_types3.Provenance.FRONTIER,
1488
- lastObserved: ts,
1489
- callCount: (existing.callCount ?? 0) + 1
1490
- };
1491
- graph.replaceEdgeAttributes(id, updated);
1492
- return;
1493
- }
1494
- const edge = {
1495
- id,
1496
- source,
1497
- target,
1498
- type,
1499
- provenance: import_types3.Provenance.FRONTIER,
1500
- confidence: 1,
1501
- lastObserved: ts,
1502
- callCount: 1
1503
- };
1504
- graph.addEdgeWithKey(id, source, target, edge);
1505
- }
1506
1486
  function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1507
1487
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1508
1488
  const id = makeObservedEdgeId(type, source, target);
@@ -1588,18 +1568,28 @@ async function appendErrorEvent(ctx, ev) {
1588
1568
  await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
1589
1569
  await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
1590
1570
  }
1571
+ function sanitizeAttributes(attrs) {
1572
+ const out = {};
1573
+ for (const [k, v] of Object.entries(attrs)) {
1574
+ if (typeof v === "bigint") out[k] = v.toString();
1575
+ else out[k] = v;
1576
+ }
1577
+ return out;
1578
+ }
1591
1579
  function buildErrorEventForReceiver(span) {
1592
1580
  if (span.statusCode !== 2) return null;
1593
1581
  const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
1582
+ const attrs = sanitizeAttributes(span.attributes);
1594
1583
  return {
1595
1584
  id: `${span.traceId}:${span.spanId}`,
1596
1585
  timestamp: ts,
1597
1586
  service: span.service,
1598
1587
  traceId: span.traceId,
1599
1588
  spanId: span.spanId,
1600
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1589
+ errorMessage: span.exception?.message ?? "unknown error",
1601
1590
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1602
1591
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1592
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1603
1593
  affectedNode: (0, import_types3.serviceId)(span.service)
1604
1594
  };
1605
1595
  }
@@ -1650,11 +1640,16 @@ async function handleSpan(ctx, span) {
1650
1640
  affectedNode = targetId;
1651
1641
  resolvedViaAddress = true;
1652
1642
  } else if (!targetId) {
1653
- const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
1654
- if (ctx.graph.hasNode(sourceId)) {
1655
- upsertFrontierEdge(ctx.graph, import_types3.EdgeType.CALLS, sourceId, frontierId2, ts);
1656
- }
1657
- affectedNode = frontierId2;
1643
+ const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
1644
+ upsertObservedEdge(
1645
+ ctx.graph,
1646
+ import_types3.EdgeType.CALLS,
1647
+ sourceId,
1648
+ frontierNodeId,
1649
+ ts,
1650
+ isError
1651
+ );
1652
+ affectedNode = frontierNodeId;
1658
1653
  resolvedViaAddress = true;
1659
1654
  }
1660
1655
  }
@@ -1676,15 +1671,17 @@ async function handleSpan(ctx, span) {
1676
1671
  if (span.statusCode === 2) {
1677
1672
  stitchTrace(ctx.graph, sourceId, ts);
1678
1673
  if (ctx.writeErrorEventInline !== false) {
1674
+ const attrs = sanitizeAttributes(span.attributes);
1679
1675
  const ev = {
1680
1676
  id: `${span.traceId}:${span.spanId}`,
1681
1677
  timestamp: ts,
1682
1678
  service: span.service,
1683
1679
  traceId: span.traceId,
1684
1680
  spanId: span.spanId,
1685
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1681
+ errorMessage: span.exception?.message ?? "unknown error",
1686
1682
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1687
1683
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1684
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1688
1685
  affectedNode
1689
1686
  };
1690
1687
  await appendErrorEvent(ctx, ev);
@@ -1740,8 +1737,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
1740
1737
  }
1741
1738
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1742
1739
  graph.dropEdge(oldEdgeId);
1743
- const promotedProvenance = edge.provenance === import_types3.Provenance.FRONTIER ? import_types3.Provenance.OBSERVED : edge.provenance;
1744
- const newId = promotedProvenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.EXTRACTED ? (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.frontierEdgeId)(newSource, newTarget, edge.type);
1740
+ const newId = edge.provenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type);
1745
1741
  if (graph.hasEdge(newId)) {
1746
1742
  const existing = graph.getEdgeAttributes(newId);
1747
1743
  const merged = {
@@ -1756,8 +1752,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1756
1752
  ...edge,
1757
1753
  id: newId,
1758
1754
  source: newSource,
1759
- target: newTarget,
1760
- provenance: promotedProvenance
1755
+ target: newTarget
1761
1756
  };
1762
1757
  graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
1763
1758
  }
@@ -3981,7 +3976,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3981
3976
  init_cjs_shims();
3982
3977
  var import_node_fs18 = require("fs");
3983
3978
  var import_node_path31 = __toESM(require("path"), 1);
3984
- var SCHEMA_VERSION = 2;
3979
+ var import_types19 = require("@neat.is/types");
3980
+ var SCHEMA_VERSION = 3;
3985
3981
  function migrateV1ToV2(payload) {
3986
3982
  const nodes = payload.graph.nodes;
3987
3983
  if (Array.isArray(nodes)) {
@@ -3993,6 +3989,25 @@ function migrateV1ToV2(payload) {
3993
3989
  }
3994
3990
  return { ...payload, schemaVersion: 2 };
3995
3991
  }
3992
+ function migrateV2ToV3(payload) {
3993
+ const edges = payload.graph.edges;
3994
+ if (Array.isArray(edges)) {
3995
+ for (const edge of edges) {
3996
+ const attrs = edge.attributes;
3997
+ if (!attrs || attrs.provenance !== "FRONTIER") continue;
3998
+ attrs.provenance = import_types19.Provenance.OBSERVED;
3999
+ const type = typeof attrs.type === "string" ? attrs.type : void 0;
4000
+ const source = typeof attrs.source === "string" ? attrs.source : void 0;
4001
+ const target = typeof attrs.target === "string" ? attrs.target : void 0;
4002
+ if (type && source && target) {
4003
+ const newId = (0, import_types19.observedEdgeId)(source, target, type);
4004
+ attrs.id = newId;
4005
+ if (edge.key) edge.key = newId;
4006
+ }
4007
+ }
4008
+ }
4009
+ return { ...payload, schemaVersion: 3 };
4010
+ }
3996
4011
  async function ensureDir(filePath) {
3997
4012
  await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
3998
4013
  }
@@ -4019,6 +4034,9 @@ async function loadGraphFromDisk(graph, outPath) {
4019
4034
  if (payload.schemaVersion === 1) {
4020
4035
  payload = migrateV1ToV2(payload);
4021
4036
  }
4037
+ if (payload.schemaVersion === 2) {
4038
+ payload = migrateV2ToV3(payload);
4039
+ }
4022
4040
  if (payload.schemaVersion !== SCHEMA_VERSION) {
4023
4041
  throw new Error(
4024
4042
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -4117,11 +4135,11 @@ var Projects = class {
4117
4135
  init_cjs_shims();
4118
4136
  var import_fastify = __toESM(require("fastify"), 1);
4119
4137
  var import_cors = __toESM(require("@fastify/cors"), 1);
4120
- var import_types21 = require("@neat.is/types");
4138
+ var import_types22 = require("@neat.is/types");
4121
4139
 
4122
4140
  // src/divergences.ts
4123
4141
  init_cjs_shims();
4124
- var import_types19 = require("@neat.is/types");
4142
+ var import_types20 = require("@neat.is/types");
4125
4143
  function bucketKey(source, target, type) {
4126
4144
  return `${type}|${source}|${target}`;
4127
4145
  }
@@ -4129,25 +4147,22 @@ function bucketEdges(graph) {
4129
4147
  const buckets = /* @__PURE__ */ new Map();
4130
4148
  graph.forEachEdge((id, attrs) => {
4131
4149
  const e = attrs;
4132
- const parsed = (0, import_types19.parseEdgeId)(id);
4150
+ const parsed = (0, import_types20.parseEdgeId)(id);
4133
4151
  const provenance = parsed?.provenance ?? e.provenance;
4134
4152
  const key = bucketKey(e.source, e.target, e.type);
4135
4153
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4136
4154
  switch (provenance) {
4137
- case import_types19.Provenance.EXTRACTED:
4155
+ case import_types20.Provenance.EXTRACTED:
4138
4156
  cur.extracted = e;
4139
4157
  break;
4140
- case import_types19.Provenance.OBSERVED:
4158
+ case import_types20.Provenance.OBSERVED:
4141
4159
  cur.observed = e;
4142
4160
  break;
4143
- case import_types19.Provenance.INFERRED:
4161
+ case import_types20.Provenance.INFERRED:
4144
4162
  cur.inferred = e;
4145
4163
  break;
4146
- case import_types19.Provenance.FRONTIER:
4147
- cur.frontier = e;
4148
- break;
4149
4164
  default:
4150
- if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
4165
+ if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
4151
4166
  }
4152
4167
  buckets.set(key, cur);
4153
4168
  });
@@ -4156,7 +4171,7 @@ function bucketEdges(graph) {
4156
4171
  function nodeIsFrontier(graph, nodeId) {
4157
4172
  if (!graph.hasNode(nodeId)) return false;
4158
4173
  const attrs = graph.getNodeAttributes(nodeId);
4159
- return attrs.type === import_types19.NodeType.FrontierNode;
4174
+ return attrs.type === import_types20.NodeType.FrontierNode;
4160
4175
  }
4161
4176
  function clampConfidence(n) {
4162
4177
  if (!Number.isFinite(n)) return 0;
@@ -4217,7 +4232,7 @@ function declaredHostFor(svc) {
4217
4232
  function hasExtractedConfiguredBy(graph, svcId) {
4218
4233
  for (const edgeId of graph.outboundEdges(svcId)) {
4219
4234
  const e = graph.getEdgeAttributes(edgeId);
4220
- if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
4235
+ if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
4221
4236
  return true;
4222
4237
  }
4223
4238
  }
@@ -4230,10 +4245,10 @@ function detectHostMismatch(graph, svcId, svc) {
4230
4245
  const out = [];
4231
4246
  for (const edgeId of graph.outboundEdges(svcId)) {
4232
4247
  const edge = graph.getEdgeAttributes(edgeId);
4233
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4234
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4248
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4249
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4235
4250
  const target = graph.getNodeAttributes(edge.target);
4236
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4251
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4237
4252
  const observedHost = target.host?.trim();
4238
4253
  if (!observedHost) continue;
4239
4254
  if (observedHost === declaredHost) continue;
@@ -4255,10 +4270,10 @@ function detectCompatDivergences(graph, svcId, svc) {
4255
4270
  const deps = svc.dependencies ?? {};
4256
4271
  for (const edgeId of graph.outboundEdges(svcId)) {
4257
4272
  const edge = graph.getEdgeAttributes(edgeId);
4258
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4259
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4273
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4274
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4260
4275
  const target = graph.getNodeAttributes(edge.target);
4261
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4276
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4262
4277
  for (const pair of compatPairs()) {
4263
4278
  if (pair.engine !== target.engine) continue;
4264
4279
  const declared = deps[pair.driver];
@@ -4319,7 +4334,7 @@ function computeDivergences(graph, opts = {}) {
4319
4334
  }
4320
4335
  graph.forEachNode((nodeId, attrs) => {
4321
4336
  const n = attrs;
4322
- if (n.type !== import_types19.NodeType.ServiceNode) return;
4337
+ if (n.type !== import_types20.NodeType.ServiceNode) return;
4323
4338
  const svc = n;
4324
4339
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
4325
4340
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -4352,7 +4367,7 @@ function computeDivergences(graph, opts = {}) {
4352
4367
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4353
4368
  return a.target.localeCompare(b.target);
4354
4369
  });
4355
- return import_types19.DivergenceResultSchema.parse({
4370
+ return import_types20.DivergenceResultSchema.parse({
4356
4371
  divergences: filtered,
4357
4372
  totalAffected: filtered.length,
4358
4373
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4441,7 +4456,7 @@ init_cjs_shims();
4441
4456
  var import_node_fs20 = require("fs");
4442
4457
  var import_node_os2 = __toESM(require("os"), 1);
4443
4458
  var import_node_path33 = __toESM(require("path"), 1);
4444
- var import_types20 = require("@neat.is/types");
4459
+ var import_types21 = require("@neat.is/types");
4445
4460
  var LOCK_TIMEOUT_MS = 5e3;
4446
4461
  var LOCK_RETRY_MS = 50;
4447
4462
  function neatHome() {
@@ -4512,10 +4527,10 @@ async function readRegistry() {
4512
4527
  throw err;
4513
4528
  }
4514
4529
  const parsed = JSON.parse(raw);
4515
- return import_types20.RegistryFileSchema.parse(parsed);
4530
+ return import_types21.RegistryFileSchema.parse(parsed);
4516
4531
  }
4517
4532
  async function writeRegistry(reg) {
4518
- const validated = import_types20.RegistryFileSchema.parse(reg);
4533
+ const validated = import_types21.RegistryFileSchema.parse(reg);
4519
4534
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
4520
4535
  }
4521
4536
  async function getProject(name) {
@@ -4725,11 +4740,11 @@ function registerRoutes(scope, ctx) {
4725
4740
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
4726
4741
  const parsed = [];
4727
4742
  for (const c of candidates) {
4728
- const r = import_types21.DivergenceTypeSchema.safeParse(c);
4743
+ const r = import_types22.DivergenceTypeSchema.safeParse(c);
4729
4744
  if (!r.success) {
4730
4745
  return reply.code(400).send({
4731
4746
  error: `unknown divergence type "${c}"`,
4732
- allowed: import_types21.DivergenceTypeSchema.options
4747
+ allowed: import_types22.DivergenceTypeSchema.options
4733
4748
  });
4734
4749
  }
4735
4750
  parsed.push(r.data);
@@ -4913,7 +4928,7 @@ function registerRoutes(scope, ctx) {
4913
4928
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
4914
4929
  let violations = await log.readAll();
4915
4930
  if (req2.query.severity) {
4916
- const sev = import_types21.PolicySeveritySchema.safeParse(req2.query.severity);
4931
+ const sev = import_types22.PolicySeveritySchema.safeParse(req2.query.severity);
4917
4932
  if (!sev.success) {
4918
4933
  return reply.code(400).send({
4919
4934
  error: "invalid severity",
@@ -4930,7 +4945,7 @@ function registerRoutes(scope, ctx) {
4930
4945
  scope.post("/policies/check", async (req2, reply) => {
4931
4946
  const proj = resolveProject(registry, req2, reply);
4932
4947
  if (!proj) return;
4933
- const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
4948
+ const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
4934
4949
  if (!parsed.success) {
4935
4950
  return reply.code(400).send({
4936
4951
  error: "invalid /policies/check body",
@@ -5375,7 +5390,83 @@ async function spawnWebUI(restPort) {
5375
5390
  return { child, port, stop };
5376
5391
  }
5377
5392
 
5393
+ // src/version-skew.ts
5394
+ init_cjs_shims();
5395
+ var import_promises = require("timers/promises");
5396
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/neat.is/latest";
5397
+ var DEFAULT_TIMEOUT_MS = 2e3;
5398
+ async function fetchRegistryVersion(url, timeoutMs, fetchImpl) {
5399
+ const controller = new AbortController();
5400
+ let timedOut = false;
5401
+ const timer = (0, import_promises.setTimeout)(timeoutMs).then(() => {
5402
+ timedOut = true;
5403
+ controller.abort();
5404
+ });
5405
+ try {
5406
+ const res = await Promise.race([
5407
+ fetchImpl(url, { signal: controller.signal, headers: { accept: "application/json" } }),
5408
+ timer.then(() => null)
5409
+ ]);
5410
+ if (timedOut || !res) return null;
5411
+ if (!res.ok) return null;
5412
+ const body = await res.json();
5413
+ if (typeof body.version !== "string" || body.version.length === 0) return null;
5414
+ return body.version;
5415
+ } catch {
5416
+ return null;
5417
+ } finally {
5418
+ if (!timedOut) controller.abort();
5419
+ }
5420
+ }
5421
+ function isLocalBehind(local, remote) {
5422
+ if (local === remote) return false;
5423
+ const parse10 = (v) => {
5424
+ const core = v.split(/[-+]/)[0] ?? v;
5425
+ return core.split(".").map((s) => {
5426
+ const n = Number.parseInt(s, 10);
5427
+ return Number.isFinite(n) ? n : 0;
5428
+ });
5429
+ };
5430
+ const a = parse10(local);
5431
+ const b = parse10(remote);
5432
+ const len = Math.max(a.length, b.length);
5433
+ for (let i = 0; i < len; i++) {
5434
+ const av = a[i] ?? 0;
5435
+ const bv = b[i] ?? 0;
5436
+ if (av < bv) return true;
5437
+ if (av > bv) return false;
5438
+ }
5439
+ return false;
5440
+ }
5441
+ async function checkVersionSkew(opts) {
5442
+ const url = opts.registryUrl ?? NPM_REGISTRY_URL;
5443
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5444
+ const fetchImpl = opts.fetchImpl ?? fetch;
5445
+ const warn = opts.warn ?? ((m) => console.warn(m));
5446
+ const remote = await fetchRegistryVersion(url, timeoutMs, fetchImpl);
5447
+ if (!remote) return { remoteVersion: null, skewed: false, warned: false };
5448
+ if (!isLocalBehind(opts.localVersion, remote)) {
5449
+ return { remoteVersion: remote, skewed: false, warned: false };
5450
+ }
5451
+ warn(
5452
+ `[neatd] running neat.is@${opts.localVersion} but neat.is@${remote} is on npm \u2014 run \`npm install -g neat.is@latest\` to update`
5453
+ );
5454
+ return { remoteVersion: remote, skewed: true, warned: true };
5455
+ }
5456
+
5378
5457
  // src/neatd.ts
5458
+ function localVersion() {
5459
+ if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
5460
+ return process.env.NEAT_LOCAL_VERSION;
5461
+ }
5462
+ try {
5463
+ const req2 = (0, import_node_module.createRequire)(importMetaUrl);
5464
+ const pkg = req2("../package.json");
5465
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
5466
+ } catch {
5467
+ return "0.0.0";
5468
+ }
5469
+ }
5379
5470
  function neatHome2() {
5380
5471
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
5381
5472
  return import_node_path38.default.resolve(process.env.NEAT_HOME);
@@ -5405,6 +5496,10 @@ async function cmdStart() {
5405
5496
  console.log(`neatd: registry at ${registryPath()}`);
5406
5497
  if (handle.restAddress) console.log(`neatd: REST \u2192 ${handle.restAddress}`);
5407
5498
  if (handle.otlpAddress) console.log(`neatd: OTLP \u2192 ${handle.otlpAddress}`);
5499
+ if (process.env.NEAT_DISABLE_VERSION_CHECK !== "1") {
5500
+ void checkVersionSkew({ localVersion: localVersion() }).catch(() => {
5501
+ });
5502
+ }
5408
5503
  const skipWeb = process.env.NEAT_WEB_DISABLED === "1";
5409
5504
  let web = null;
5410
5505
  if (!skipWeb) {