@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/{chunk-2AJHEZIB.js → chunk-5MGHWP6R.js} +2 -2
- package/dist/{chunk-4M2YMGUM.js → chunk-QQDZSIY3.js} +101 -33
- package/dist/chunk-QQDZSIY3.js.map +1 -0
- package/dist/cli.cjs +97 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +97 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +97 -31
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +97 -31
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-4M2YMGUM.js.map +0 -1
- /package/dist/{chunk-2AJHEZIB.js.map → chunk-5MGHWP6R.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -1283,7 +1283,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
|
|
|
1283
1283
|
for (const id of walk3.path) {
|
|
1284
1284
|
const owner = resolveOwningService(graph, id);
|
|
1285
1285
|
if (!owner) continue;
|
|
1286
|
-
const { id:
|
|
1286
|
+
const { id: serviceId6, svc } = owner;
|
|
1287
1287
|
const deps = svc.dependencies ?? {};
|
|
1288
1288
|
for (const pair of candidatePairs) {
|
|
1289
1289
|
const declared = deps[pair.driver];
|
|
@@ -1296,7 +1296,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
|
|
|
1296
1296
|
);
|
|
1297
1297
|
if (!result.compatible) {
|
|
1298
1298
|
return {
|
|
1299
|
-
rootCauseNode:
|
|
1299
|
+
rootCauseNode: serviceId6,
|
|
1300
1300
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1301
1301
|
...result.minDriverVersion ? {
|
|
1302
1302
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1311,7 +1311,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
|
|
|
1311
1311
|
for (const id of walk3.path) {
|
|
1312
1312
|
const owner = resolveOwningService(graph, id);
|
|
1313
1313
|
if (!owner) continue;
|
|
1314
|
-
const { id:
|
|
1314
|
+
const { id: serviceId6, svc } = owner;
|
|
1315
1315
|
const deps = svc.dependencies ?? {};
|
|
1316
1316
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1317
1317
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1320,7 +1320,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
|
|
|
1320
1320
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1321
1321
|
if (!result.compatible && result.reason) {
|
|
1322
1322
|
return {
|
|
1323
|
-
rootCauseNode:
|
|
1323
|
+
rootCauseNode: serviceId6,
|
|
1324
1324
|
rootCauseReason: result.reason,
|
|
1325
1325
|
...result.requiredNodeVersion ? {
|
|
1326
1326
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1335,7 +1335,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
|
|
|
1335
1335
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1336
1336
|
if (!result.compatible && result.reason) {
|
|
1337
1337
|
return {
|
|
1338
|
-
rootCauseNode:
|
|
1338
|
+
rootCauseNode: serviceId6,
|
|
1339
1339
|
rootCauseReason: result.reason,
|
|
1340
1340
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1341
1341
|
};
|
|
@@ -1426,9 +1426,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1426
1426
|
function isFailingCallEdge(e) {
|
|
1427
1427
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1428
1428
|
}
|
|
1429
|
-
function callSourcesForService(graph,
|
|
1430
|
-
const ids = [
|
|
1431
|
-
for (const edgeId of graph.outboundEdges(
|
|
1429
|
+
function callSourcesForService(graph, serviceId6) {
|
|
1430
|
+
const ids = [serviceId6];
|
|
1431
|
+
for (const edgeId of graph.outboundEdges(serviceId6)) {
|
|
1432
1432
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1433
1433
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1434
1434
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1445,9 +1445,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1445
1445
|
}
|
|
1446
1446
|
return id < curId;
|
|
1447
1447
|
}
|
|
1448
|
-
function dominantFailingCall(graph,
|
|
1448
|
+
function dominantFailingCall(graph, serviceId6, visited) {
|
|
1449
1449
|
let best = null;
|
|
1450
|
-
for (const src of callSourcesForService(graph,
|
|
1450
|
+
for (const src of callSourcesForService(graph, serviceId6)) {
|
|
1451
1451
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1452
1452
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1453
1453
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -2616,6 +2616,27 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
|
2616
2616
|
graph.addNode(id, node);
|
|
2617
2617
|
return id;
|
|
2618
2618
|
}
|
|
2619
|
+
function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
|
|
2620
|
+
if (!graph.hasNode(serviceNodeId)) return null;
|
|
2621
|
+
const sources = [serviceNodeId];
|
|
2622
|
+
for (const edgeId of graph.outboundEdges(serviceNodeId)) {
|
|
2623
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
2624
|
+
if (e.type === import_types3.EdgeType.CONTAINS) sources.push(e.target);
|
|
2625
|
+
}
|
|
2626
|
+
const matches = /* @__PURE__ */ new Set();
|
|
2627
|
+
for (const src of sources) {
|
|
2628
|
+
if (!graph.hasNode(src)) continue;
|
|
2629
|
+
for (const edgeId of graph.outboundEdges(src)) {
|
|
2630
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
2631
|
+
if (edge.type !== import_types3.EdgeType.CONNECTS_TO || edge.provenance !== import_types3.Provenance.EXTRACTED) continue;
|
|
2632
|
+
if (!graph.hasNode(edge.target)) continue;
|
|
2633
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
2634
|
+
if (target.type !== import_types3.NodeType.DatabaseNode || target.engine !== engine) continue;
|
|
2635
|
+
matches.add(edge.target);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
return matches.size === 1 ? [...matches][0] : null;
|
|
2639
|
+
}
|
|
2619
2640
|
function ensureFrontierNode(graph, host, ts) {
|
|
2620
2641
|
const id = frontierIdFor(host);
|
|
2621
2642
|
if (graph.hasNode(id)) {
|
|
@@ -2874,13 +2895,18 @@ async function handleSpan(ctx, span) {
|
|
|
2874
2895
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2875
2896
|
targetId = (0, import_types3.databaseId)(host);
|
|
2876
2897
|
} else {
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
localName
|
|
2882
|
-
|
|
2883
|
-
|
|
2898
|
+
const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
|
|
2899
|
+
if (declared) {
|
|
2900
|
+
targetId = declared;
|
|
2901
|
+
} else {
|
|
2902
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2903
|
+
targetId = ensureLocalDatabaseNode(
|
|
2904
|
+
ctx.graph,
|
|
2905
|
+
span.service,
|
|
2906
|
+
localName,
|
|
2907
|
+
span.dbSystem
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2884
2910
|
}
|
|
2885
2911
|
const result = upsertObservedEdge(
|
|
2886
2912
|
ctx.graph,
|
|
@@ -3064,29 +3090,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
3064
3090
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
3065
3091
|
});
|
|
3066
3092
|
let promoted = 0;
|
|
3067
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
3093
|
+
for (const { frontierId: frontierId2, serviceId: serviceId6 } of toPromote) {
|
|
3068
3094
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
3069
3095
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
3070
3096
|
if (!gate.allowed) {
|
|
3071
3097
|
continue;
|
|
3072
3098
|
}
|
|
3073
3099
|
}
|
|
3074
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
3100
|
+
rewireFrontierEdges(graph, frontierId2, serviceId6);
|
|
3075
3101
|
graph.dropNode(frontierId2);
|
|
3076
3102
|
promoted++;
|
|
3077
3103
|
}
|
|
3078
3104
|
return promoted;
|
|
3079
3105
|
}
|
|
3080
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
3106
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId6) {
|
|
3081
3107
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
3082
3108
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
3083
3109
|
for (const edgeId of inbound) {
|
|
3084
3110
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3085
|
-
rebuildEdge(graph, edge, edge.source,
|
|
3111
|
+
rebuildEdge(graph, edge, edge.source, serviceId6, edgeId);
|
|
3086
3112
|
}
|
|
3087
3113
|
for (const edgeId of outbound) {
|
|
3088
3114
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3089
|
-
rebuildEdge(graph, edge,
|
|
3115
|
+
rebuildEdge(graph, edge, serviceId6, edge.target, edgeId);
|
|
3090
3116
|
}
|
|
3091
3117
|
}
|
|
3092
3118
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -3894,9 +3920,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
3894
3920
|
"StatefulSet",
|
|
3895
3921
|
"DaemonSet"
|
|
3896
3922
|
]);
|
|
3897
|
-
function addAliases(graph,
|
|
3898
|
-
if (!graph.hasNode(
|
|
3899
|
-
const node = graph.getNodeAttributes(
|
|
3923
|
+
function addAliases(graph, serviceId6, candidates) {
|
|
3924
|
+
if (!graph.hasNode(serviceId6)) return;
|
|
3925
|
+
const node = graph.getNodeAttributes(serviceId6);
|
|
3900
3926
|
if (node.type !== import_types6.NodeType.ServiceNode) return;
|
|
3901
3927
|
const set = new Set(node.aliases ?? []);
|
|
3902
3928
|
for (const c of candidates) {
|
|
@@ -3906,7 +3932,7 @@ function addAliases(graph, serviceId5, candidates) {
|
|
|
3906
3932
|
}
|
|
3907
3933
|
if (set.size === 0) return;
|
|
3908
3934
|
const updated = { ...node, aliases: [...set].sort() };
|
|
3909
|
-
graph.replaceNodeAttributes(
|
|
3935
|
+
graph.replaceNodeAttributes(serviceId6, updated);
|
|
3910
3936
|
}
|
|
3911
3937
|
function indexServicesByName(services) {
|
|
3912
3938
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3939,12 +3965,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3939
3965
|
}
|
|
3940
3966
|
if (!compose?.services) return;
|
|
3941
3967
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3942
|
-
const
|
|
3943
|
-
if (!
|
|
3968
|
+
const serviceId6 = serviceIndex.get(composeName);
|
|
3969
|
+
if (!serviceId6) continue;
|
|
3944
3970
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3945
3971
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3946
3972
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3947
|
-
addAliases(graph,
|
|
3973
|
+
addAliases(graph, serviceId6, aliases);
|
|
3948
3974
|
}
|
|
3949
3975
|
}
|
|
3950
3976
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -4594,6 +4620,29 @@ async function readIfExists(filePath) {
|
|
|
4594
4620
|
return null;
|
|
4595
4621
|
}
|
|
4596
4622
|
}
|
|
4623
|
+
async function resolveEnvVar(serviceDir, name) {
|
|
4624
|
+
const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
4625
|
+
const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
|
|
4626
|
+
const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
|
|
4627
|
+
envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
4628
|
+
for (const fileName of envNames) {
|
|
4629
|
+
const content = await readIfExists(import_node_path15.default.join(serviceDir, fileName));
|
|
4630
|
+
if (!content) continue;
|
|
4631
|
+
for (const line of content.split("\n")) {
|
|
4632
|
+
const trimmed = line.trim();
|
|
4633
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
4634
|
+
const eq = trimmed.indexOf("=");
|
|
4635
|
+
if (eq < 0) continue;
|
|
4636
|
+
if (trimmed.slice(0, eq).trim() !== name) continue;
|
|
4637
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
4638
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
4639
|
+
value = value.slice(1, -1);
|
|
4640
|
+
}
|
|
4641
|
+
return value || null;
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
return null;
|
|
4645
|
+
}
|
|
4597
4646
|
async function findFirst(serviceDir, candidates) {
|
|
4598
4647
|
for (const rel of candidates) {
|
|
4599
4648
|
const abs = import_node_path15.default.join(serviceDir, rel);
|
|
@@ -4691,6 +4740,14 @@ async function parse3(serviceDir) {
|
|
|
4691
4740
|
const config = parseConnectionString(urlMatch[1]);
|
|
4692
4741
|
if (config) return [{ ...config, sourceFile: schemaPath }];
|
|
4693
4742
|
}
|
|
4743
|
+
const envMatch = body.match(/url\s*=\s*env\(\s*"([^"]+)"\s*\)/);
|
|
4744
|
+
if (envMatch) {
|
|
4745
|
+
const resolved = await resolveEnvVar(serviceDir, envMatch[1]);
|
|
4746
|
+
if (resolved) {
|
|
4747
|
+
const config = parseConnectionString(resolved);
|
|
4748
|
+
if (config) return [{ ...config, sourceFile: schemaPath }];
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4694
4751
|
return [
|
|
4695
4752
|
{
|
|
4696
4753
|
host: `${engine}-prisma`,
|
|
@@ -7448,14 +7505,23 @@ var import_types30 = require("@neat.is/types");
|
|
|
7448
7505
|
function bucketKey(source, target, type) {
|
|
7449
7506
|
return `${type}|${source}|${target}`;
|
|
7450
7507
|
}
|
|
7508
|
+
function bucketSourceFor(graph, edge) {
|
|
7509
|
+
if (edge.type !== import_types30.EdgeType.CONNECTS_TO) return edge.source;
|
|
7510
|
+
const parsed = (0, import_types30.parseFileId)(edge.source);
|
|
7511
|
+
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
7512
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
7513
|
+
if (target.type !== import_types30.NodeType.DatabaseNode) return edge.source;
|
|
7514
|
+
return (0, import_types30.serviceId)(parsed.service);
|
|
7515
|
+
}
|
|
7451
7516
|
function bucketEdges(graph) {
|
|
7452
7517
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
7453
7518
|
graph.forEachEdge((id, attrs) => {
|
|
7454
7519
|
const e = attrs;
|
|
7455
7520
|
const parsed = (0, import_types30.parseEdgeId)(id);
|
|
7456
7521
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
7457
|
-
const
|
|
7458
|
-
const
|
|
7522
|
+
const source = bucketSourceFor(graph, e);
|
|
7523
|
+
const key = bucketKey(source, e.target, e.type);
|
|
7524
|
+
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
7459
7525
|
switch (provenance) {
|
|
7460
7526
|
case import_types30.Provenance.EXTRACTED:
|
|
7461
7527
|
cur.extracted = e;
|