@neat.is/core 0.7.4 → 0.7.5
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-UU4XKSTO.js → chunk-KB4HRB6N.js} +872 -119
- package/dist/chunk-KB4HRB6N.js.map +1 -0
- package/dist/{chunk-KN6O7KMK.js → chunk-MYLUXVXE.js} +2 -2
- package/dist/cli.cjs +1632 -328
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +647 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +892 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +892 -133
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +860 -101
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-UU4XKSTO.js.map +0 -1
- /package/dist/{chunk-KN6O7KMK.js.map → chunk-MYLUXVXE.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -736,7 +736,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
736
736
|
init_cjs_shims();
|
|
737
737
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
738
738
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
739
|
-
var
|
|
739
|
+
var import_types66 = require("@neat.is/types");
|
|
740
740
|
|
|
741
741
|
// src/extend/index.ts
|
|
742
742
|
init_cjs_shims();
|
|
@@ -8947,6 +8947,55 @@ function columnsFromClassBody(body) {
|
|
|
8947
8947
|
}
|
|
8948
8948
|
return out;
|
|
8949
8949
|
}
|
|
8950
|
+
function foreignKeyParentTable(call) {
|
|
8951
|
+
const fn = call.childForFieldName("function");
|
|
8952
|
+
const t = fn?.text;
|
|
8953
|
+
if (!t) return null;
|
|
8954
|
+
const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
|
|
8955
|
+
if (base !== "ForeignKey") return null;
|
|
8956
|
+
const target = firstPositionalString(call);
|
|
8957
|
+
if (!target) return null;
|
|
8958
|
+
const parts = target.split(".");
|
|
8959
|
+
if (parts.length < 2) return null;
|
|
8960
|
+
return parts[parts.length - 2];
|
|
8961
|
+
}
|
|
8962
|
+
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
8963
|
+
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8964
|
+
const tree = parseSource6(makePyParser4(), file.content);
|
|
8965
|
+
const out = [];
|
|
8966
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8967
|
+
walk3(tree.rootNode, (node) => {
|
|
8968
|
+
if (node.type !== "class_definition") return;
|
|
8969
|
+
const body = node.childForFieldName("body");
|
|
8970
|
+
const nameNode = node.childForFieldName("name");
|
|
8971
|
+
if (!body || !nameNode) return;
|
|
8972
|
+
const explicit = explicitTablename(body);
|
|
8973
|
+
if (explicit === "computed") return;
|
|
8974
|
+
let childTable = null;
|
|
8975
|
+
if (explicit) childTable = explicit.name;
|
|
8976
|
+
else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
|
|
8977
|
+
if (!childTable) return;
|
|
8978
|
+
walk3(body, (n) => {
|
|
8979
|
+
if (n.type !== "call") return;
|
|
8980
|
+
const parentTable = foreignKeyParentTable(n);
|
|
8981
|
+
if (!parentTable) return;
|
|
8982
|
+
const key = `${childTable}->${parentTable}`;
|
|
8983
|
+
if (seen.has(key)) return;
|
|
8984
|
+
seen.add(key);
|
|
8985
|
+
const line = n.startPosition.row + 1;
|
|
8986
|
+
out.push({
|
|
8987
|
+
childTable,
|
|
8988
|
+
parentTable,
|
|
8989
|
+
evidence: {
|
|
8990
|
+
file: import_node_path37.default.relative(serviceDir, file.path),
|
|
8991
|
+
line,
|
|
8992
|
+
snippet: snippet(file.content, line)
|
|
8993
|
+
}
|
|
8994
|
+
});
|
|
8995
|
+
});
|
|
8996
|
+
});
|
|
8997
|
+
return out;
|
|
8998
|
+
}
|
|
8950
8999
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
8951
9000
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8952
9001
|
const tree = parseSource6(makePyParser4(), file.content);
|
|
@@ -9292,6 +9341,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
9292
9341
|
walk6(tree.rootNode);
|
|
9293
9342
|
return out;
|
|
9294
9343
|
}
|
|
9344
|
+
function enclosingVarName(call) {
|
|
9345
|
+
let node = call;
|
|
9346
|
+
while (node?.parent) {
|
|
9347
|
+
const parent = node.parent;
|
|
9348
|
+
if (parent.type === "variable_declarator") {
|
|
9349
|
+
const name = parent.childForFieldName("name");
|
|
9350
|
+
return name?.type === "identifier" ? name.text : null;
|
|
9351
|
+
}
|
|
9352
|
+
if (parent.type === "call_expression" || parent.type === "member_expression") {
|
|
9353
|
+
node = parent;
|
|
9354
|
+
continue;
|
|
9355
|
+
}
|
|
9356
|
+
return null;
|
|
9357
|
+
}
|
|
9358
|
+
return null;
|
|
9359
|
+
}
|
|
9360
|
+
function collectDrizzleTables(root) {
|
|
9361
|
+
const tables = [];
|
|
9362
|
+
const varToTable = /* @__PURE__ */ new Map();
|
|
9363
|
+
const walk6 = (node) => {
|
|
9364
|
+
if (node.type === "call_expression") {
|
|
9365
|
+
const fn = node.childForFieldName("function");
|
|
9366
|
+
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
9367
|
+
const args = node.childForFieldName("arguments");
|
|
9368
|
+
const argNodes = args ? namedChildren3(args) : [];
|
|
9369
|
+
const tableName = stringLiteralText2(argNodes[0] ?? null);
|
|
9370
|
+
const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
|
|
9371
|
+
if (tableName) {
|
|
9372
|
+
tables.push({ tableName, object: obj });
|
|
9373
|
+
const varName = enclosingVarName(node);
|
|
9374
|
+
if (varName) varToTable.set(varName, tableName);
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
}
|
|
9378
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
9379
|
+
};
|
|
9380
|
+
walk6(root);
|
|
9381
|
+
return { tables, varToTable };
|
|
9382
|
+
}
|
|
9383
|
+
function referencesTargetVar(call) {
|
|
9384
|
+
const fn = call.childForFieldName("function");
|
|
9385
|
+
if (fn?.type !== "member_expression") return null;
|
|
9386
|
+
if (fn.childForFieldName("property")?.text !== "references") return null;
|
|
9387
|
+
const args = call.childForFieldName("arguments");
|
|
9388
|
+
const first = args ? namedChildren3(args)[0] ?? null : null;
|
|
9389
|
+
if (first?.type !== "arrow_function") return null;
|
|
9390
|
+
const body = first.childForFieldName("body");
|
|
9391
|
+
if (body?.type !== "member_expression") return null;
|
|
9392
|
+
const obj = body.childForFieldName("object");
|
|
9393
|
+
return obj?.type === "identifier" ? obj.text : null;
|
|
9394
|
+
}
|
|
9395
|
+
function drizzleForeignKeys(file, serviceDir) {
|
|
9396
|
+
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9397
|
+
const tree = parseSource3(parserForExt(import_node_path39.default.extname(file.path)), file.content);
|
|
9398
|
+
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
9399
|
+
const out = [];
|
|
9400
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9401
|
+
for (const table of tables) {
|
|
9402
|
+
if (!table.object) continue;
|
|
9403
|
+
const walk6 = (node) => {
|
|
9404
|
+
if (node.type === "call_expression") {
|
|
9405
|
+
const targetVar = referencesTargetVar(node);
|
|
9406
|
+
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
9407
|
+
if (parentTable) {
|
|
9408
|
+
const key = `${table.tableName}->${parentTable}`;
|
|
9409
|
+
if (!seen.has(key)) {
|
|
9410
|
+
seen.add(key);
|
|
9411
|
+
const line = node.startPosition.row + 1;
|
|
9412
|
+
out.push({
|
|
9413
|
+
childTable: table.tableName,
|
|
9414
|
+
parentTable,
|
|
9415
|
+
evidence: {
|
|
9416
|
+
file: import_node_path39.default.relative(serviceDir, file.path),
|
|
9417
|
+
line,
|
|
9418
|
+
snippet: snippet(file.content, line)
|
|
9419
|
+
}
|
|
9420
|
+
});
|
|
9421
|
+
}
|
|
9422
|
+
}
|
|
9423
|
+
}
|
|
9424
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
9425
|
+
};
|
|
9426
|
+
walk6(table.object);
|
|
9427
|
+
}
|
|
9428
|
+
return out;
|
|
9429
|
+
}
|
|
9295
9430
|
|
|
9296
9431
|
// src/extract/calls/prisma.ts
|
|
9297
9432
|
init_cjs_shims();
|
|
@@ -9420,6 +9555,96 @@ async function prismaColumnEndpoints(serviceDir) {
|
|
|
9420
9555
|
if (!content) return [];
|
|
9421
9556
|
return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
|
|
9422
9557
|
}
|
|
9558
|
+
function buildModelTableMap(lines) {
|
|
9559
|
+
const map = /* @__PURE__ */ new Map();
|
|
9560
|
+
let current = null;
|
|
9561
|
+
let depth = 0;
|
|
9562
|
+
for (const raw of lines) {
|
|
9563
|
+
if (current === null) {
|
|
9564
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
9565
|
+
if (header && raw.includes("{")) {
|
|
9566
|
+
current = { model: header[1], table: header[1] };
|
|
9567
|
+
depth = netBraces(raw);
|
|
9568
|
+
if (depth <= 0) {
|
|
9569
|
+
map.set(current.model, current.table);
|
|
9570
|
+
current = null;
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
continue;
|
|
9574
|
+
}
|
|
9575
|
+
depth += netBraces(raw);
|
|
9576
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9577
|
+
if (trimmed.startsWith("@@")) {
|
|
9578
|
+
const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
|
|
9579
|
+
if (m) current.table = m[1];
|
|
9580
|
+
}
|
|
9581
|
+
if (depth <= 0) {
|
|
9582
|
+
map.set(current.model, current.table);
|
|
9583
|
+
current = null;
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
if (current) map.set(current.model, current.table);
|
|
9587
|
+
return map;
|
|
9588
|
+
}
|
|
9589
|
+
function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
9590
|
+
const content = file.content;
|
|
9591
|
+
if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
|
|
9592
|
+
const lines = content.split("\n");
|
|
9593
|
+
const modelToTable = buildModelTableMap(lines);
|
|
9594
|
+
const out = [];
|
|
9595
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9596
|
+
let current = null;
|
|
9597
|
+
let depth = 0;
|
|
9598
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9599
|
+
const raw = lines[i];
|
|
9600
|
+
const lineNo = i + 1;
|
|
9601
|
+
if (current === null) {
|
|
9602
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
9603
|
+
if (header && raw.includes("{")) {
|
|
9604
|
+
current = { table: modelToTable.get(header[1]) ?? header[1] };
|
|
9605
|
+
depth = netBraces(raw);
|
|
9606
|
+
if (depth <= 0) current = null;
|
|
9607
|
+
}
|
|
9608
|
+
continue;
|
|
9609
|
+
}
|
|
9610
|
+
depth += netBraces(raw);
|
|
9611
|
+
const closing = depth <= 0;
|
|
9612
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9613
|
+
if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
|
|
9614
|
+
const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
|
|
9615
|
+
if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
|
|
9616
|
+
const parentTable = modelToTable.get(fm[2]);
|
|
9617
|
+
if (parentTable) {
|
|
9618
|
+
const key = `${current.table}->${parentTable}`;
|
|
9619
|
+
if (!seen.has(key)) {
|
|
9620
|
+
seen.add(key);
|
|
9621
|
+
out.push({
|
|
9622
|
+
childTable: current.table,
|
|
9623
|
+
parentTable,
|
|
9624
|
+
evidence: {
|
|
9625
|
+
file: import_node_path40.default.relative(serviceDir, file.path),
|
|
9626
|
+
line: lineNo,
|
|
9627
|
+
snippet: snippet(content, lineNo)
|
|
9628
|
+
}
|
|
9629
|
+
});
|
|
9630
|
+
}
|
|
9631
|
+
}
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
if (closing) current = null;
|
|
9635
|
+
}
|
|
9636
|
+
return out;
|
|
9637
|
+
}
|
|
9638
|
+
async function prismaForeignKeys(serviceDir) {
|
|
9639
|
+
const schemaPath = await findFirst(serviceDir, [
|
|
9640
|
+
import_node_path40.default.join("prisma", "schema.prisma"),
|
|
9641
|
+
"schema.prisma"
|
|
9642
|
+
]);
|
|
9643
|
+
if (!schemaPath) return [];
|
|
9644
|
+
const content = await readIfExists(schemaPath);
|
|
9645
|
+
if (!content) return [];
|
|
9646
|
+
return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
|
|
9647
|
+
}
|
|
9423
9648
|
|
|
9424
9649
|
// src/extract/calls/go.ts
|
|
9425
9650
|
init_cjs_shims();
|
|
@@ -9596,21 +9821,79 @@ async function addCallEdges(graph, services) {
|
|
|
9596
9821
|
};
|
|
9597
9822
|
}
|
|
9598
9823
|
|
|
9824
|
+
// src/extract/table-edges.ts
|
|
9825
|
+
init_cjs_shims();
|
|
9826
|
+
var import_types32 = require("@neat.is/types");
|
|
9827
|
+
async function addTableEdges(graph, services) {
|
|
9828
|
+
let nodesAdded = 0;
|
|
9829
|
+
let edgesAdded = 0;
|
|
9830
|
+
for (const service of services) {
|
|
9831
|
+
const files = await loadSourceFiles(service.dir);
|
|
9832
|
+
const refs = [];
|
|
9833
|
+
for (const file of files) {
|
|
9834
|
+
try {
|
|
9835
|
+
refs.push(...drizzleForeignKeys(file, service.dir));
|
|
9836
|
+
refs.push(...sqlalchemyForeignKeys(file, service.dir));
|
|
9837
|
+
} catch (err) {
|
|
9838
|
+
recordExtractionError("foreign-key extraction", file.path, err);
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
try {
|
|
9842
|
+
refs.push(...await prismaForeignKeys(service.dir));
|
|
9843
|
+
} catch (err) {
|
|
9844
|
+
recordExtractionError("prisma foreign-key extraction", service.dir, err);
|
|
9845
|
+
}
|
|
9846
|
+
for (const ref of refs) {
|
|
9847
|
+
const childId = (0, import_types32.infraId)("sql-table", ref.childTable);
|
|
9848
|
+
const parentId = (0, import_types32.infraId)("sql-table", ref.parentTable);
|
|
9849
|
+
if (childId === parentId) continue;
|
|
9850
|
+
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
9851
|
+
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
9852
|
+
const edgeId = (0, import_types32.extractedEdgeId)(childId, parentId, import_types32.EdgeType.REFERENCES);
|
|
9853
|
+
if (graph.hasEdge(edgeId)) continue;
|
|
9854
|
+
const edge = {
|
|
9855
|
+
id: edgeId,
|
|
9856
|
+
source: childId,
|
|
9857
|
+
target: parentId,
|
|
9858
|
+
type: import_types32.EdgeType.REFERENCES,
|
|
9859
|
+
provenance: import_types32.Provenance.EXTRACTED,
|
|
9860
|
+
confidence: (0, import_types32.confidenceForExtracted)("structural"),
|
|
9861
|
+
evidence: ref.evidence
|
|
9862
|
+
};
|
|
9863
|
+
graph.addEdgeWithKey(edgeId, childId, parentId, edge);
|
|
9864
|
+
edgesAdded++;
|
|
9865
|
+
}
|
|
9866
|
+
}
|
|
9867
|
+
return { nodesAdded, edgesAdded };
|
|
9868
|
+
}
|
|
9869
|
+
function ensureTableNode(graph, id, name) {
|
|
9870
|
+
if (graph.hasNode(id)) return 0;
|
|
9871
|
+
const node = {
|
|
9872
|
+
id,
|
|
9873
|
+
type: import_types32.NodeType.InfraNode,
|
|
9874
|
+
name,
|
|
9875
|
+
provider: "self",
|
|
9876
|
+
kind: "sql-table"
|
|
9877
|
+
};
|
|
9878
|
+
graph.addNode(id, node);
|
|
9879
|
+
return 1;
|
|
9880
|
+
}
|
|
9881
|
+
|
|
9599
9882
|
// src/extract/infra/index.ts
|
|
9600
9883
|
init_cjs_shims();
|
|
9601
9884
|
|
|
9602
9885
|
// src/extract/infra/docker-compose.ts
|
|
9603
9886
|
init_cjs_shims();
|
|
9604
9887
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
9605
|
-
var
|
|
9888
|
+
var import_types34 = require("@neat.is/types");
|
|
9606
9889
|
|
|
9607
9890
|
// src/extract/infra/shared.ts
|
|
9608
9891
|
init_cjs_shims();
|
|
9609
|
-
var
|
|
9892
|
+
var import_types33 = require("@neat.is/types");
|
|
9610
9893
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
9611
9894
|
return {
|
|
9612
|
-
id: (0,
|
|
9613
|
-
type:
|
|
9895
|
+
id: (0, import_types33.infraId)(kind, name),
|
|
9896
|
+
type: import_types33.NodeType.InfraNode,
|
|
9614
9897
|
name,
|
|
9615
9898
|
provider,
|
|
9616
9899
|
kind,
|
|
@@ -9654,8 +9937,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
9654
9937
|
source: anchorId,
|
|
9655
9938
|
target: node.id,
|
|
9656
9939
|
type: edgeType,
|
|
9657
|
-
provenance:
|
|
9658
|
-
confidence: (0,
|
|
9940
|
+
provenance: import_types33.Provenance.EXTRACTED,
|
|
9941
|
+
confidence: (0, import_types33.confidenceForExtracted)("structural"),
|
|
9659
9942
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9660
9943
|
};
|
|
9661
9944
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9722,15 +10005,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9722
10005
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
9723
10006
|
const targetId = composeNameToNodeId.get(dep);
|
|
9724
10007
|
if (!targetId) continue;
|
|
9725
|
-
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId,
|
|
10008
|
+
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types34.EdgeType.DEPENDS_ON);
|
|
9726
10009
|
if (graph.hasEdge(edgeId)) continue;
|
|
9727
10010
|
const edge = {
|
|
9728
10011
|
id: edgeId,
|
|
9729
10012
|
source: sourceId,
|
|
9730
10013
|
target: targetId,
|
|
9731
|
-
type:
|
|
9732
|
-
provenance:
|
|
9733
|
-
confidence: (0,
|
|
10014
|
+
type: import_types34.EdgeType.DEPENDS_ON,
|
|
10015
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
10016
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9734
10017
|
evidence: { file: evidenceFile }
|
|
9735
10018
|
};
|
|
9736
10019
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9744,7 +10027,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9744
10027
|
init_cjs_shims();
|
|
9745
10028
|
var import_node_path45 = __toESM(require("path"), 1);
|
|
9746
10029
|
var import_node_fs19 = require("fs");
|
|
9747
|
-
var
|
|
10030
|
+
var import_types35 = require("@neat.is/types");
|
|
9748
10031
|
function readDockerfile(content) {
|
|
9749
10032
|
let image = null;
|
|
9750
10033
|
const ports = [];
|
|
@@ -9803,15 +10086,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9803
10086
|
);
|
|
9804
10087
|
nodesAdded += fn;
|
|
9805
10088
|
edgesAdded += fe;
|
|
9806
|
-
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id,
|
|
10089
|
+
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types35.EdgeType.RUNS_ON);
|
|
9807
10090
|
if (!graph.hasEdge(edgeId)) {
|
|
9808
10091
|
const edge = {
|
|
9809
10092
|
id: edgeId,
|
|
9810
10093
|
source: fileNodeId,
|
|
9811
10094
|
target: node.id,
|
|
9812
|
-
type:
|
|
9813
|
-
provenance:
|
|
9814
|
-
confidence: (0,
|
|
10095
|
+
type: import_types35.EdgeType.RUNS_ON,
|
|
10096
|
+
provenance: import_types35.Provenance.EXTRACTED,
|
|
10097
|
+
confidence: (0, import_types35.confidenceForExtracted)("structural"),
|
|
9815
10098
|
evidence: {
|
|
9816
10099
|
file: evidenceFile,
|
|
9817
10100
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -9826,15 +10109,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9826
10109
|
graph.addNode(portNode.id, portNode);
|
|
9827
10110
|
nodesAdded++;
|
|
9828
10111
|
}
|
|
9829
|
-
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id,
|
|
10112
|
+
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types35.EdgeType.CONNECTS_TO);
|
|
9830
10113
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
9831
10114
|
const portEdge = {
|
|
9832
10115
|
id: portEdgeId,
|
|
9833
10116
|
source: fileNodeId,
|
|
9834
10117
|
target: portNode.id,
|
|
9835
|
-
type:
|
|
9836
|
-
provenance:
|
|
9837
|
-
confidence: (0,
|
|
10118
|
+
type: import_types35.EdgeType.CONNECTS_TO,
|
|
10119
|
+
provenance: import_types35.Provenance.EXTRACTED,
|
|
10120
|
+
confidence: (0, import_types35.confidenceForExtracted)("structural"),
|
|
9838
10121
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
9839
10122
|
};
|
|
9840
10123
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -9848,7 +10131,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9848
10131
|
init_cjs_shims();
|
|
9849
10132
|
var import_node_fs20 = require("fs");
|
|
9850
10133
|
var import_node_path46 = __toESM(require("path"), 1);
|
|
9851
|
-
var
|
|
10134
|
+
var import_types36 = require("@neat.is/types");
|
|
9852
10135
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
9853
10136
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
9854
10137
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -9929,16 +10212,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
9929
10212
|
if (!target) continue;
|
|
9930
10213
|
if (seen.has(target.nodeId)) continue;
|
|
9931
10214
|
seen.add(target.nodeId);
|
|
9932
|
-
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
10215
|
+
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types36.EdgeType.DEPENDS_ON);
|
|
9933
10216
|
if (graph.hasEdge(edgeId)) continue;
|
|
9934
10217
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
9935
10218
|
const edge = {
|
|
9936
10219
|
id: edgeId,
|
|
9937
10220
|
source: resource.nodeId,
|
|
9938
10221
|
target: target.nodeId,
|
|
9939
|
-
type:
|
|
9940
|
-
provenance:
|
|
9941
|
-
confidence: (0,
|
|
10222
|
+
type: import_types36.EdgeType.DEPENDS_ON,
|
|
10223
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
10224
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9942
10225
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
9943
10226
|
};
|
|
9944
10227
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -10010,7 +10293,7 @@ init_cjs_shims();
|
|
|
10010
10293
|
var import_node_fs22 = require("fs");
|
|
10011
10294
|
var import_node_path48 = __toESM(require("path"), 1);
|
|
10012
10295
|
var import_smol_toml2 = require("smol-toml");
|
|
10013
|
-
var
|
|
10296
|
+
var import_types37 = require("@neat.is/types");
|
|
10014
10297
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
10015
10298
|
async function readWranglerConfig(dir) {
|
|
10016
10299
|
for (const filename of WRANGLER_FILENAMES) {
|
|
@@ -10058,8 +10341,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
10058
10341
|
source: anchorId,
|
|
10059
10342
|
target: node.id,
|
|
10060
10343
|
type: edgeType,
|
|
10061
|
-
provenance:
|
|
10062
|
-
confidence: (0,
|
|
10344
|
+
provenance: import_types37.Provenance.EXTRACTED,
|
|
10345
|
+
confidence: (0, import_types37.confidenceForExtracted)("structural"),
|
|
10063
10346
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
10064
10347
|
};
|
|
10065
10348
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -10120,15 +10403,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10120
10403
|
nodesAdded++;
|
|
10121
10404
|
}
|
|
10122
10405
|
if (runtimeNode.id !== anchorId) {
|
|
10123
|
-
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
10406
|
+
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types37.EdgeType.RUNS_ON);
|
|
10124
10407
|
if (!graph.hasEdge(runsOnId)) {
|
|
10125
10408
|
const edge = {
|
|
10126
10409
|
id: runsOnId,
|
|
10127
10410
|
source: anchorId,
|
|
10128
10411
|
target: runtimeNode.id,
|
|
10129
|
-
type:
|
|
10130
|
-
provenance:
|
|
10131
|
-
confidence: (0,
|
|
10412
|
+
type: import_types37.EdgeType.RUNS_ON,
|
|
10413
|
+
provenance: import_types37.Provenance.EXTRACTED,
|
|
10414
|
+
confidence: (0, import_types37.confidenceForExtracted)("structural"),
|
|
10132
10415
|
evidence: {
|
|
10133
10416
|
file: evidenceFile,
|
|
10134
10417
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -10142,7 +10425,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10142
10425
|
const result = addResourceEdge(
|
|
10143
10426
|
graph,
|
|
10144
10427
|
anchorId,
|
|
10145
|
-
|
|
10428
|
+
import_types37.EdgeType.CONNECTS_TO,
|
|
10146
10429
|
"cloudflare-route",
|
|
10147
10430
|
route,
|
|
10148
10431
|
evidenceFile,
|
|
@@ -10166,7 +10449,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10166
10449
|
const result = addResourceEdge(
|
|
10167
10450
|
graph,
|
|
10168
10451
|
anchorId,
|
|
10169
|
-
|
|
10452
|
+
import_types37.EdgeType.DEPENDS_ON,
|
|
10170
10453
|
group.kind,
|
|
10171
10454
|
name,
|
|
10172
10455
|
evidenceFile,
|
|
@@ -10180,7 +10463,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10180
10463
|
const result = addResourceEdge(
|
|
10181
10464
|
graph,
|
|
10182
10465
|
anchorId,
|
|
10183
|
-
|
|
10466
|
+
import_types37.EdgeType.DEPENDS_ON,
|
|
10184
10467
|
"cloudflare-cron",
|
|
10185
10468
|
cron,
|
|
10186
10469
|
evidenceFile,
|
|
@@ -10193,7 +10476,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10193
10476
|
const result = addResourceEdge(
|
|
10194
10477
|
graph,
|
|
10195
10478
|
anchorId,
|
|
10196
|
-
|
|
10479
|
+
import_types37.EdgeType.DEPENDS_ON,
|
|
10197
10480
|
"cloudflare-env-var",
|
|
10198
10481
|
varName,
|
|
10199
10482
|
evidenceFile,
|
|
@@ -10206,15 +10489,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10206
10489
|
if (!svc.service) continue;
|
|
10207
10490
|
const target = workerIndex.get(svc.service);
|
|
10208
10491
|
if (target && target.anchorId !== anchorId) {
|
|
10209
|
-
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId,
|
|
10492
|
+
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types37.EdgeType.CALLS);
|
|
10210
10493
|
if (!graph.hasEdge(edgeId)) {
|
|
10211
10494
|
const edge = {
|
|
10212
10495
|
id: edgeId,
|
|
10213
10496
|
source: anchorId,
|
|
10214
10497
|
target: target.anchorId,
|
|
10215
|
-
type:
|
|
10216
|
-
provenance:
|
|
10217
|
-
confidence: (0,
|
|
10498
|
+
type: import_types37.EdgeType.CALLS,
|
|
10499
|
+
provenance: import_types37.Provenance.EXTRACTED,
|
|
10500
|
+
confidence: (0, import_types37.confidenceForExtracted)("structural"),
|
|
10218
10501
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
10219
10502
|
};
|
|
10220
10503
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -10225,7 +10508,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10225
10508
|
const result = addResourceEdge(
|
|
10226
10509
|
graph,
|
|
10227
10510
|
anchorId,
|
|
10228
|
-
|
|
10511
|
+
import_types37.EdgeType.DEPENDS_ON,
|
|
10229
10512
|
"cloudflare-service-binding",
|
|
10230
10513
|
svc.service,
|
|
10231
10514
|
evidenceFile,
|
|
@@ -10242,7 +10525,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
10242
10525
|
init_cjs_shims();
|
|
10243
10526
|
var import_node_fs23 = require("fs");
|
|
10244
10527
|
var import_node_path49 = __toESM(require("path"), 1);
|
|
10245
|
-
var
|
|
10528
|
+
var import_types38 = require("@neat.is/types");
|
|
10246
10529
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
10247
10530
|
async function readVercelConfig(dir) {
|
|
10248
10531
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
@@ -10305,12 +10588,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
10305
10588
|
nodesAdded += result.nodesAdded;
|
|
10306
10589
|
edgesAdded += result.edgesAdded;
|
|
10307
10590
|
};
|
|
10308
|
-
add(
|
|
10309
|
-
for (const cron of config.crons ?? []) add(
|
|
10310
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
10311
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
10591
|
+
add(import_types38.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
10592
|
+
for (const cron of config.crons ?? []) add(import_types38.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
10593
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types38.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
10594
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types38.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
10312
10595
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
10313
|
-
add(
|
|
10596
|
+
add(import_types38.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
10314
10597
|
}
|
|
10315
10598
|
}
|
|
10316
10599
|
return { nodesAdded, edgesAdded };
|
|
@@ -10321,7 +10604,7 @@ init_cjs_shims();
|
|
|
10321
10604
|
var import_node_fs24 = require("fs");
|
|
10322
10605
|
var import_node_path50 = __toESM(require("path"), 1);
|
|
10323
10606
|
var import_smol_toml3 = require("smol-toml");
|
|
10324
|
-
var
|
|
10607
|
+
var import_types39 = require("@neat.is/types");
|
|
10325
10608
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
10326
10609
|
async function readRailwayConfig(dir) {
|
|
10327
10610
|
for (const filename of RAILWAY_FILENAMES) {
|
|
@@ -10367,9 +10650,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
10367
10650
|
nodesAdded += result.nodesAdded;
|
|
10368
10651
|
edgesAdded += result.edgesAdded;
|
|
10369
10652
|
};
|
|
10370
|
-
add(
|
|
10371
|
-
add(
|
|
10372
|
-
add(
|
|
10653
|
+
add(import_types39.EdgeType.RUNS_ON, "railway", "railway");
|
|
10654
|
+
add(import_types39.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
10655
|
+
add(import_types39.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
10373
10656
|
}
|
|
10374
10657
|
return { nodesAdded, edgesAdded };
|
|
10375
10658
|
}
|
|
@@ -10379,7 +10662,7 @@ init_cjs_shims();
|
|
|
10379
10662
|
var import_node_fs25 = require("fs");
|
|
10380
10663
|
var import_node_path51 = __toESM(require("path"), 1);
|
|
10381
10664
|
var import_smol_toml4 = require("smol-toml");
|
|
10382
|
-
var
|
|
10665
|
+
var import_types40 = require("@neat.is/types");
|
|
10383
10666
|
async function readSupabaseConfig(dir) {
|
|
10384
10667
|
const relFile = import_node_path51.default.join("supabase", "config.toml");
|
|
10385
10668
|
const abs = import_node_path51.default.join(dir, relFile);
|
|
@@ -10427,10 +10710,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
10427
10710
|
nodesAdded += result.nodesAdded;
|
|
10428
10711
|
edgesAdded += result.edgesAdded;
|
|
10429
10712
|
};
|
|
10430
|
-
add(
|
|
10431
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
10432
|
-
if (config.storage) add(
|
|
10433
|
-
if (config.auth) add(
|
|
10713
|
+
add(import_types40.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
10714
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types40.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
10715
|
+
if (config.storage) add(import_types40.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
10716
|
+
if (config.auth) add(import_types40.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
10434
10717
|
}
|
|
10435
10718
|
return { nodesAdded, edgesAdded };
|
|
10436
10719
|
}
|
|
@@ -10458,11 +10741,11 @@ var import_node_path53 = __toESM(require("path"), 1);
|
|
|
10458
10741
|
init_cjs_shims();
|
|
10459
10742
|
var import_node_fs26 = require("fs");
|
|
10460
10743
|
var import_node_path52 = __toESM(require("path"), 1);
|
|
10461
|
-
var
|
|
10744
|
+
var import_types41 = require("@neat.is/types");
|
|
10462
10745
|
function dropOrphanedFileNodes(graph) {
|
|
10463
10746
|
const orphans = [];
|
|
10464
10747
|
graph.forEachNode((id, attrs) => {
|
|
10465
|
-
if (attrs.type !==
|
|
10748
|
+
if (attrs.type !== import_types41.NodeType.FileNode) return;
|
|
10466
10749
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
10467
10750
|
orphans.push(id);
|
|
10468
10751
|
}
|
|
@@ -10475,7 +10758,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
10475
10758
|
const bases = [scanPath, ...serviceDirs];
|
|
10476
10759
|
graph.forEachEdge((id, attrs) => {
|
|
10477
10760
|
const edge = attrs;
|
|
10478
|
-
if (edge.provenance !==
|
|
10761
|
+
if (edge.provenance !== import_types41.Provenance.EXTRACTED) return;
|
|
10479
10762
|
const evidenceFile = edge.evidence?.file;
|
|
10480
10763
|
if (!evidenceFile) return;
|
|
10481
10764
|
if (import_node_path52.default.isAbsolute(evidenceFile)) {
|
|
@@ -10506,6 +10789,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
10506
10789
|
const routePhase = await addRoutes(graph, services);
|
|
10507
10790
|
const grpcPhase = await addGrpcMethods(graph, services);
|
|
10508
10791
|
const phase4 = await addCallEdges(graph, services);
|
|
10792
|
+
const tableEdges = await addTableEdges(graph, services);
|
|
10509
10793
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
10510
10794
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
10511
10795
|
graph,
|
|
@@ -10545,8 +10829,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
10545
10829
|
}
|
|
10546
10830
|
}
|
|
10547
10831
|
const result = {
|
|
10548
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
10549
|
-
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
10832
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
|
|
10833
|
+
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
|
|
10550
10834
|
frontiersPromoted,
|
|
10551
10835
|
extractionErrors: errorEntries.length,
|
|
10552
10836
|
errorEntries,
|
|
@@ -10648,7 +10932,7 @@ function canonicalJson(value) {
|
|
|
10648
10932
|
init_cjs_shims();
|
|
10649
10933
|
var import_node_fs28 = require("fs");
|
|
10650
10934
|
var import_node_path54 = __toESM(require("path"), 1);
|
|
10651
|
-
var
|
|
10935
|
+
var import_types42 = require("@neat.is/types");
|
|
10652
10936
|
var SCHEMA_VERSION = 6;
|
|
10653
10937
|
function migrateV1ToV2(payload) {
|
|
10654
10938
|
const nodes = payload.graph.nodes;
|
|
@@ -10672,7 +10956,7 @@ function migrateV5ToV6(payload) {
|
|
|
10672
10956
|
if (Array.isArray(nodes)) {
|
|
10673
10957
|
for (const node of nodes) {
|
|
10674
10958
|
const attrs = node.attributes;
|
|
10675
|
-
if (!attrs || attrs.type !==
|
|
10959
|
+
if (!attrs || attrs.type !== import_types42.NodeType.InfraNode) continue;
|
|
10676
10960
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
10677
10961
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
10678
10962
|
}
|
|
@@ -10685,12 +10969,12 @@ function migrateV2ToV3(payload) {
|
|
|
10685
10969
|
for (const edge of edges) {
|
|
10686
10970
|
const attrs = edge.attributes;
|
|
10687
10971
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
10688
|
-
attrs.provenance =
|
|
10972
|
+
attrs.provenance = import_types42.Provenance.OBSERVED;
|
|
10689
10973
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
10690
10974
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
10691
10975
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
10692
10976
|
if (type && source && target) {
|
|
10693
|
-
const newId = (0,
|
|
10977
|
+
const newId = (0, import_types42.observedEdgeId)(source, target, type);
|
|
10694
10978
|
attrs.id = newId;
|
|
10695
10979
|
if (edge.key) edge.key = newId;
|
|
10696
10980
|
}
|
|
@@ -10845,7 +11129,7 @@ init_cjs_shims();
|
|
|
10845
11129
|
var import_node_fs29 = require("fs");
|
|
10846
11130
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
10847
11131
|
var import_node_path56 = __toESM(require("path"), 1);
|
|
10848
|
-
var
|
|
11132
|
+
var import_types43 = require("@neat.is/types");
|
|
10849
11133
|
function neatHome() {
|
|
10850
11134
|
const override = process.env.NEAT_HOME;
|
|
10851
11135
|
if (override && override.length > 0) return import_node_path56.default.resolve(override);
|
|
@@ -10935,7 +11219,7 @@ async function readRegistry() {
|
|
|
10935
11219
|
throw err;
|
|
10936
11220
|
}
|
|
10937
11221
|
const parsed = JSON.parse(raw);
|
|
10938
|
-
return
|
|
11222
|
+
return import_types43.RegistryFileSchema.parse(parsed);
|
|
10939
11223
|
}
|
|
10940
11224
|
async function getProject(name) {
|
|
10941
11225
|
const reg = await readRegistry();
|
|
@@ -11216,15 +11500,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
11216
11500
|
|
|
11217
11501
|
// src/connectors/index.ts
|
|
11218
11502
|
init_cjs_shims();
|
|
11219
|
-
var
|
|
11503
|
+
var import_types44 = require("@neat.is/types");
|
|
11220
11504
|
var NO_ENV = "unknown";
|
|
11221
11505
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
11222
11506
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11223
11507
|
const sites = [];
|
|
11224
11508
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
11225
11509
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
11226
|
-
if (edge.provenance !==
|
|
11227
|
-
const parsed = (0,
|
|
11510
|
+
if (edge.provenance !== import_types44.Provenance.EXTRACTED) continue;
|
|
11511
|
+
const parsed = (0, import_types44.parseFileId)(edge.source);
|
|
11228
11512
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
11229
11513
|
const site = { relPath: edge.evidence.file };
|
|
11230
11514
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -11235,7 +11519,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
11235
11519
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
11236
11520
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11237
11521
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
11238
|
-
if (attrs.type !==
|
|
11522
|
+
if (attrs.type !== import_types44.NodeType.RouteNode || !attrs.path) return void 0;
|
|
11239
11523
|
const site = { relPath: attrs.path };
|
|
11240
11524
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
11241
11525
|
return site;
|
|
@@ -11378,7 +11662,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
|
|
|
11378
11662
|
// connector add/remove/test` (provision/deprovision/validate), never a poll
|
|
11379
11663
|
// loop, so this bucket is exercised a handful of times per command. Kept
|
|
11380
11664
|
// conservative pending a documented Drains-API rate limit.
|
|
11381
|
-
vercel: { capacity: 20, refillMs: 5e3 }
|
|
11665
|
+
vercel: { capacity: 20, refillMs: 5e3 },
|
|
11666
|
+
// Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
|
|
11667
|
+
// pinned here to a single confirmed number — this is a conservative
|
|
11668
|
+
// placeholder pending a live project, matching the other pull providers.
|
|
11669
|
+
render: { capacity: 30, refillMs: 1e4 }
|
|
11382
11670
|
};
|
|
11383
11671
|
var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
|
|
11384
11672
|
function defaultRateLimitFor(provider) {
|
|
@@ -11785,23 +12073,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
11785
12073
|
|
|
11786
12074
|
// src/connectors/supabase/resolve.ts
|
|
11787
12075
|
init_cjs_shims();
|
|
11788
|
-
var
|
|
12076
|
+
var import_types46 = require("@neat.is/types");
|
|
11789
12077
|
function createSupabaseResolveTarget(graph, config) {
|
|
11790
12078
|
return (signal, _ctx) => {
|
|
11791
12079
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
11792
12080
|
return null;
|
|
11793
12081
|
}
|
|
11794
|
-
const subResourceId = (0,
|
|
12082
|
+
const subResourceId = (0, import_types46.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
11795
12083
|
if (graph.hasNode(subResourceId)) {
|
|
11796
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
12084
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
11797
12085
|
}
|
|
11798
|
-
const bareResourceId = (0,
|
|
12086
|
+
const bareResourceId = (0, import_types46.infraId)(signal.targetKind, signal.targetName);
|
|
11799
12087
|
if (graph.hasNode(bareResourceId)) {
|
|
11800
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
12088
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
11801
12089
|
}
|
|
11802
|
-
const projectLevelId = (0,
|
|
12090
|
+
const projectLevelId = (0, import_types46.infraId)("supabase", config.nodeRef);
|
|
11803
12091
|
if (graph.hasNode(projectLevelId)) {
|
|
11804
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
12092
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
11805
12093
|
}
|
|
11806
12094
|
return null;
|
|
11807
12095
|
};
|
|
@@ -11894,7 +12182,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
11894
12182
|
|
|
11895
12183
|
// src/connectors/railway/index.ts
|
|
11896
12184
|
init_cjs_shims();
|
|
11897
|
-
var
|
|
12185
|
+
var import_types50 = require("@neat.is/types");
|
|
11898
12186
|
|
|
11899
12187
|
// src/connectors/railway/client.ts
|
|
11900
12188
|
init_cjs_shims();
|
|
@@ -12045,7 +12333,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
12045
12333
|
const out = [];
|
|
12046
12334
|
graph.forEachNode((_id, attrs) => {
|
|
12047
12335
|
const node = attrs;
|
|
12048
|
-
if (node.type !==
|
|
12336
|
+
if (node.type !== import_types50.NodeType.RouteNode) return;
|
|
12049
12337
|
const route = attrs;
|
|
12050
12338
|
if (route.service !== serviceName) return;
|
|
12051
12339
|
out.push({
|
|
@@ -12149,12 +12437,12 @@ function createRailwayResolveTarget(config) {
|
|
|
12149
12437
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
12150
12438
|
if (!serviceName) return null;
|
|
12151
12439
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
12152
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
12440
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types50.EdgeType.CALLS };
|
|
12153
12441
|
}
|
|
12154
12442
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
12155
12443
|
const peerName = config.serviceNameById[signal.targetName];
|
|
12156
12444
|
if (!peerName) return null;
|
|
12157
|
-
return { targetNodeId: (0,
|
|
12445
|
+
return { targetNodeId: (0, import_types50.serviceId)(peerName), serviceName, edgeType: import_types50.EdgeType.CONNECTS_TO };
|
|
12158
12446
|
}
|
|
12159
12447
|
return null;
|
|
12160
12448
|
};
|
|
@@ -12342,7 +12630,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
12342
12630
|
|
|
12343
12631
|
// src/connectors/firebase/resolve.ts
|
|
12344
12632
|
init_cjs_shims();
|
|
12345
|
-
var
|
|
12633
|
+
var import_types51 = require("@neat.is/types");
|
|
12346
12634
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
12347
12635
|
switch (resourceType) {
|
|
12348
12636
|
case "cloud_function":
|
|
@@ -12357,7 +12645,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
12357
12645
|
const entries = [];
|
|
12358
12646
|
graph.forEachNode((_id, attrs) => {
|
|
12359
12647
|
const node = attrs;
|
|
12360
|
-
if (node.type !==
|
|
12648
|
+
if (node.type !== import_types51.NodeType.RouteNode) return;
|
|
12361
12649
|
const route = attrs;
|
|
12362
12650
|
if (route.service !== serviceName) return;
|
|
12363
12651
|
entries.push({
|
|
@@ -12389,7 +12677,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
12389
12677
|
return {
|
|
12390
12678
|
targetNodeId: match.routeNodeId,
|
|
12391
12679
|
serviceName,
|
|
12392
|
-
edgeType:
|
|
12680
|
+
edgeType: import_types51.EdgeType.CALLS
|
|
12393
12681
|
};
|
|
12394
12682
|
};
|
|
12395
12683
|
}
|
|
@@ -12416,7 +12704,7 @@ init_cjs_shims();
|
|
|
12416
12704
|
|
|
12417
12705
|
// src/connectors/cloudflare/connector.ts
|
|
12418
12706
|
init_cjs_shims();
|
|
12419
|
-
var
|
|
12707
|
+
var import_types53 = require("@neat.is/types");
|
|
12420
12708
|
|
|
12421
12709
|
// src/connectors/cloudflare/client.ts
|
|
12422
12710
|
init_cjs_shims();
|
|
@@ -12580,7 +12868,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
12580
12868
|
graph.forEachNode((id, attrs) => {
|
|
12581
12869
|
if (found) return;
|
|
12582
12870
|
const a = attrs;
|
|
12583
|
-
if (a.type ===
|
|
12871
|
+
if (a.type === import_types53.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
12584
12872
|
found = id;
|
|
12585
12873
|
}
|
|
12586
12874
|
});
|
|
@@ -12592,7 +12880,7 @@ function findMatchingRouteNode(graph, serviceName, method, path60) {
|
|
|
12592
12880
|
graph.forEachNode((id, attrs) => {
|
|
12593
12881
|
if (found) return;
|
|
12594
12882
|
const a = attrs;
|
|
12595
|
-
if (a.type !==
|
|
12883
|
+
if (a.type !== import_types53.NodeType.RouteNode || a.service !== serviceName) return;
|
|
12596
12884
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
12597
12885
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
12598
12886
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -12611,11 +12899,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
12611
12899
|
};
|
|
12612
12900
|
const mapping = config.workers?.[scriptName];
|
|
12613
12901
|
if (mapping) {
|
|
12614
|
-
const wholeFileId = (0,
|
|
12902
|
+
const wholeFileId = (0, import_types53.fileId)(mapping.service, mapping.entryFile);
|
|
12615
12903
|
return {
|
|
12616
12904
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
12617
12905
|
serviceName: mapping.service,
|
|
12618
|
-
edgeType:
|
|
12906
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
12619
12907
|
};
|
|
12620
12908
|
}
|
|
12621
12909
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -12624,13 +12912,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
12624
12912
|
return {
|
|
12625
12913
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
12626
12914
|
serviceName: fileNode.service,
|
|
12627
|
-
edgeType:
|
|
12915
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
12628
12916
|
};
|
|
12629
12917
|
}
|
|
12630
12918
|
return {
|
|
12631
|
-
targetNodeId: (0,
|
|
12919
|
+
targetNodeId: (0, import_types53.infraId)("cloudflare-worker", scriptName),
|
|
12632
12920
|
serviceName: scriptName,
|
|
12633
|
-
edgeType:
|
|
12921
|
+
edgeType: import_types53.EdgeType.CALLS,
|
|
12634
12922
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
12635
12923
|
};
|
|
12636
12924
|
};
|
|
@@ -12826,14 +13114,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
12826
13114
|
|
|
12827
13115
|
// src/connectors/neon/resolve.ts
|
|
12828
13116
|
init_cjs_shims();
|
|
12829
|
-
var
|
|
13117
|
+
var import_types57 = require("@neat.is/types");
|
|
12830
13118
|
function createNeonResolveTarget(config) {
|
|
12831
13119
|
return (signal) => {
|
|
12832
13120
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
12833
13121
|
return {
|
|
12834
|
-
targetNodeId: (0,
|
|
13122
|
+
targetNodeId: (0, import_types57.infraId)("sql-table", signal.targetName),
|
|
12835
13123
|
serviceName: config.serviceName,
|
|
12836
|
-
edgeType:
|
|
13124
|
+
edgeType: import_types57.EdgeType.CALLS,
|
|
12837
13125
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
12838
13126
|
};
|
|
12839
13127
|
};
|
|
@@ -12871,6 +13159,412 @@ function createNeonConnector(config, deps = {}) {
|
|
|
12871
13159
|
};
|
|
12872
13160
|
}
|
|
12873
13161
|
|
|
13162
|
+
// src/connectors/cloud-run/index.ts
|
|
13163
|
+
init_cjs_shims();
|
|
13164
|
+
|
|
13165
|
+
// src/connectors/cloud-run/client.ts
|
|
13166
|
+
init_cjs_shims();
|
|
13167
|
+
function cloudRunRequestLogName(projectId) {
|
|
13168
|
+
return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
|
|
13169
|
+
}
|
|
13170
|
+
function buildCloudRunEntriesFilter(projectId, sinceIso) {
|
|
13171
|
+
return [
|
|
13172
|
+
`logName = "${cloudRunRequestLogName(projectId)}"`,
|
|
13173
|
+
`resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
|
|
13174
|
+
'httpRequest.requestMethod != ""',
|
|
13175
|
+
`timestamp >= "${sinceIso}"`
|
|
13176
|
+
].join(" AND ");
|
|
13177
|
+
}
|
|
13178
|
+
var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
|
|
13179
|
+
var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
|
|
13180
|
+
var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
|
|
13181
|
+
var PAGE_SIZE2 = 1e3;
|
|
13182
|
+
var MAX_PAGES2 = 20;
|
|
13183
|
+
async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
|
|
13184
|
+
const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
|
|
13185
|
+
const out = [];
|
|
13186
|
+
let pageToken;
|
|
13187
|
+
for (let page = 0; page < MAX_PAGES2; page++) {
|
|
13188
|
+
const body = {
|
|
13189
|
+
resourceNames: [`projects/${creds.projectId}`],
|
|
13190
|
+
filter,
|
|
13191
|
+
orderBy: "timestamp asc",
|
|
13192
|
+
pageSize: PAGE_SIZE2,
|
|
13193
|
+
...pageToken ? { pageToken } : {}
|
|
13194
|
+
};
|
|
13195
|
+
const res = await junctionFetch(
|
|
13196
|
+
apiUrl,
|
|
13197
|
+
{
|
|
13198
|
+
method: "POST",
|
|
13199
|
+
headers: {
|
|
13200
|
+
...bearerAuthHeader(creds.accessToken),
|
|
13201
|
+
"Content-Type": "application/json"
|
|
13202
|
+
},
|
|
13203
|
+
body: JSON.stringify(body)
|
|
13204
|
+
},
|
|
13205
|
+
// accountKey: the GCP project id — one customer's Cloud Logging quota is
|
|
13206
|
+
// scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
|
|
13207
|
+
// bucket), the same key Firebase's connector uses.
|
|
13208
|
+
{ provider: "cloud-run", accountKey: creds.projectId }
|
|
13209
|
+
);
|
|
13210
|
+
if (!res.ok) {
|
|
13211
|
+
throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
|
|
13212
|
+
}
|
|
13213
|
+
const json = await res.json();
|
|
13214
|
+
if (Array.isArray(json.entries)) out.push(...json.entries);
|
|
13215
|
+
if (!json.nextPageToken) break;
|
|
13216
|
+
pageToken = json.nextPageToken;
|
|
13217
|
+
}
|
|
13218
|
+
return out;
|
|
13219
|
+
}
|
|
13220
|
+
|
|
13221
|
+
// src/connectors/cloud-run/map.ts
|
|
13222
|
+
init_cjs_shims();
|
|
13223
|
+
|
|
13224
|
+
// src/connectors/cloud-run/types.ts
|
|
13225
|
+
init_cjs_shims();
|
|
13226
|
+
function readCloudRunCredentials(raw) {
|
|
13227
|
+
const projectId = raw["projectId"];
|
|
13228
|
+
const accessToken = raw["accessToken"];
|
|
13229
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
13230
|
+
throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
|
|
13231
|
+
}
|
|
13232
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
13233
|
+
throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
|
|
13234
|
+
}
|
|
13235
|
+
return { projectId, accessToken };
|
|
13236
|
+
}
|
|
13237
|
+
var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
|
|
13238
|
+
var FIELD_SEP2 = "\0";
|
|
13239
|
+
function packCloudRunTargetName(identity) {
|
|
13240
|
+
return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
|
|
13241
|
+
}
|
|
13242
|
+
function parseCloudRunTargetName(targetName) {
|
|
13243
|
+
const firstSep = targetName.indexOf(FIELD_SEP2);
|
|
13244
|
+
if (firstSep === -1) return null;
|
|
13245
|
+
const serviceName = targetName.slice(0, firstSep);
|
|
13246
|
+
const rest = targetName.slice(firstSep + 1);
|
|
13247
|
+
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
13248
|
+
if (secondSep === -1) return null;
|
|
13249
|
+
const method = rest.slice(0, secondSep);
|
|
13250
|
+
const path60 = rest.slice(secondSep + 1);
|
|
13251
|
+
if (!serviceName || !method || !path60) return null;
|
|
13252
|
+
return { serviceName, method, path: path60 };
|
|
13253
|
+
}
|
|
13254
|
+
|
|
13255
|
+
// src/connectors/cloud-run/map.ts
|
|
13256
|
+
var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
|
|
13257
|
+
function pathFromRequestUrl2(requestUrl) {
|
|
13258
|
+
if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
|
|
13259
|
+
if (requestUrl.startsWith("/")) {
|
|
13260
|
+
const withoutQuery = requestUrl.split("?")[0];
|
|
13261
|
+
return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
|
|
13262
|
+
}
|
|
13263
|
+
try {
|
|
13264
|
+
const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
|
|
13265
|
+
const parsed = new URL(candidate);
|
|
13266
|
+
return parsed.pathname || "/";
|
|
13267
|
+
} catch {
|
|
13268
|
+
return null;
|
|
13269
|
+
}
|
|
13270
|
+
}
|
|
13271
|
+
var ERROR_STATUS_THRESHOLD4 = 500;
|
|
13272
|
+
function mapLogEntryToSignal2(entry) {
|
|
13273
|
+
if (!entry || typeof entry !== "object") return null;
|
|
13274
|
+
if (entry.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
|
|
13275
|
+
const serviceName = entry.resource?.labels?.["service_name"];
|
|
13276
|
+
if (typeof serviceName !== "string" || serviceName.length === 0) return null;
|
|
13277
|
+
const req = entry.httpRequest;
|
|
13278
|
+
if (!req) return null;
|
|
13279
|
+
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
13280
|
+
const method = req.requestMethod.toUpperCase();
|
|
13281
|
+
const path60 = pathFromRequestUrl2(req.requestUrl);
|
|
13282
|
+
if (path60 === null) return null;
|
|
13283
|
+
const timestamp = entry.timestamp;
|
|
13284
|
+
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
13285
|
+
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
13286
|
+
return {
|
|
13287
|
+
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
13288
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path60 }),
|
|
13289
|
+
callCount: 1,
|
|
13290
|
+
errorCount: isError ? 1 : 0,
|
|
13291
|
+
lastObservedIso: timestamp
|
|
13292
|
+
};
|
|
13293
|
+
}
|
|
13294
|
+
function mapLogEntriesToSignals2(entries) {
|
|
13295
|
+
const out = [];
|
|
13296
|
+
for (const entry of entries) {
|
|
13297
|
+
const signal = mapLogEntryToSignal2(entry);
|
|
13298
|
+
if (signal) out.push(signal);
|
|
13299
|
+
}
|
|
13300
|
+
return out;
|
|
13301
|
+
}
|
|
13302
|
+
|
|
13303
|
+
// src/connectors/cloud-run/resolve.ts
|
|
13304
|
+
init_cjs_shims();
|
|
13305
|
+
var import_types61 = require("@neat.is/types");
|
|
13306
|
+
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
13307
|
+
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
13308
|
+
let found = null;
|
|
13309
|
+
graph.forEachNode((_id, attrs) => {
|
|
13310
|
+
if (found) return;
|
|
13311
|
+
const node = attrs;
|
|
13312
|
+
if (node.type !== import_types61.NodeType.RouteNode) return;
|
|
13313
|
+
const route = attrs;
|
|
13314
|
+
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
13315
|
+
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
13316
|
+
const routeMethod = route.method.toUpperCase();
|
|
13317
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
13318
|
+
found = route.id;
|
|
13319
|
+
});
|
|
13320
|
+
return found;
|
|
13321
|
+
}
|
|
13322
|
+
function createCloudRunResolveTarget(graph, config) {
|
|
13323
|
+
return (signal) => {
|
|
13324
|
+
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
13325
|
+
const identity = parseCloudRunTargetName(signal.targetName);
|
|
13326
|
+
if (!identity) return null;
|
|
13327
|
+
const { serviceName: gcpServiceName, method, path: path60 } = identity;
|
|
13328
|
+
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
13329
|
+
if (mappedService) {
|
|
13330
|
+
const routeNodeId = findMatchingRouteNode2(
|
|
13331
|
+
graph,
|
|
13332
|
+
mappedService,
|
|
13333
|
+
method,
|
|
13334
|
+
normalizePathTemplate(path60)
|
|
13335
|
+
);
|
|
13336
|
+
if (routeNodeId) {
|
|
13337
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types61.EdgeType.CALLS };
|
|
13338
|
+
}
|
|
13339
|
+
}
|
|
13340
|
+
return {
|
|
13341
|
+
targetNodeId: (0, import_types61.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
13342
|
+
serviceName: mappedService ?? gcpServiceName,
|
|
13343
|
+
edgeType: import_types61.EdgeType.CALLS,
|
|
13344
|
+
ensureInfraNode: {
|
|
13345
|
+
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
13346
|
+
name: gcpServiceName,
|
|
13347
|
+
provider: "cloud-run"
|
|
13348
|
+
}
|
|
13349
|
+
};
|
|
13350
|
+
};
|
|
13351
|
+
}
|
|
13352
|
+
|
|
13353
|
+
// src/connectors/cloud-run/index.ts
|
|
13354
|
+
var CloudRunConnector = class {
|
|
13355
|
+
constructor(config = {}) {
|
|
13356
|
+
this.config = config;
|
|
13357
|
+
}
|
|
13358
|
+
config;
|
|
13359
|
+
provider = "cloud-run";
|
|
13360
|
+
async poll(ctx) {
|
|
13361
|
+
const creds = readCloudRunCredentials(ctx.credentials);
|
|
13362
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
|
|
13363
|
+
const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
13364
|
+
const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
|
|
13365
|
+
return mapLogEntriesToSignals2(entries);
|
|
13366
|
+
}
|
|
13367
|
+
};
|
|
13368
|
+
function boundedSinceIso(since, now, maxLookbackMs) {
|
|
13369
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13370
|
+
if (!since) return floor.toISOString();
|
|
13371
|
+
const sinceMs = new Date(since).getTime();
|
|
13372
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13373
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13374
|
+
}
|
|
13375
|
+
function createCloudRunConnector(graph, config = {}) {
|
|
13376
|
+
return {
|
|
13377
|
+
connector: new CloudRunConnector(config),
|
|
13378
|
+
resolveTarget: createCloudRunResolveTarget(graph, config)
|
|
13379
|
+
};
|
|
13380
|
+
}
|
|
13381
|
+
|
|
13382
|
+
// src/connectors/render/index.ts
|
|
13383
|
+
init_cjs_shims();
|
|
13384
|
+
var import_types64 = require("@neat.is/types");
|
|
13385
|
+
|
|
13386
|
+
// src/connectors/render/types.ts
|
|
13387
|
+
init_cjs_shims();
|
|
13388
|
+
function readRenderToken(credentials) {
|
|
13389
|
+
const token = credentials.token;
|
|
13390
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
13391
|
+
throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
|
|
13392
|
+
}
|
|
13393
|
+
return token;
|
|
13394
|
+
}
|
|
13395
|
+
function renderLabelValue(entry, name) {
|
|
13396
|
+
if (!Array.isArray(entry.labels)) return void 0;
|
|
13397
|
+
const label = entry.labels.find((l) => l && typeof l === "object" && l.name === name);
|
|
13398
|
+
return label && typeof label.value === "string" ? label.value : void 0;
|
|
13399
|
+
}
|
|
13400
|
+
|
|
13401
|
+
// src/connectors/render/client.ts
|
|
13402
|
+
init_cjs_shims();
|
|
13403
|
+
var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
|
|
13404
|
+
var DEFAULT_RENDER_LOG_LIMIT = 100;
|
|
13405
|
+
var RENDER_MAX_LOG_LIMIT = 100;
|
|
13406
|
+
var DEFAULT_RENDER_MAX_PAGES = 20;
|
|
13407
|
+
var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
|
|
13408
|
+
function clampLimit(limit) {
|
|
13409
|
+
const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
|
|
13410
|
+
if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
|
|
13411
|
+
return Math.min(raw, RENDER_MAX_LOG_LIMIT);
|
|
13412
|
+
}
|
|
13413
|
+
function boundedRenderStartTime(since, now, maxLookbackMs) {
|
|
13414
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13415
|
+
if (!since) return floor.toISOString();
|
|
13416
|
+
const sinceMs = new Date(since).getTime();
|
|
13417
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13418
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13419
|
+
}
|
|
13420
|
+
async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
|
|
13421
|
+
const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
|
|
13422
|
+
url.searchParams.set("ownerId", config.ownerId);
|
|
13423
|
+
url.searchParams.set("resource", config.resourceId);
|
|
13424
|
+
url.searchParams.set("type", "request");
|
|
13425
|
+
url.searchParams.set("startTime", startTime);
|
|
13426
|
+
url.searchParams.set("endTime", endTime);
|
|
13427
|
+
url.searchParams.set("direction", "backward");
|
|
13428
|
+
url.searchParams.set("limit", String(limit));
|
|
13429
|
+
const res = await junctionFetch(
|
|
13430
|
+
url,
|
|
13431
|
+
{ method: "GET", headers: { ...bearerAuthHeader(token) } },
|
|
13432
|
+
{ provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
|
|
13433
|
+
);
|
|
13434
|
+
if (!res.ok) {
|
|
13435
|
+
throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
|
|
13436
|
+
}
|
|
13437
|
+
return await res.json();
|
|
13438
|
+
}
|
|
13439
|
+
async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
|
|
13440
|
+
const limit = clampLimit(config.limit);
|
|
13441
|
+
const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
|
|
13442
|
+
const out = [];
|
|
13443
|
+
let pageStart = startTime;
|
|
13444
|
+
let pageEnd = endTime;
|
|
13445
|
+
for (let page = 0; page < maxPages; page++) {
|
|
13446
|
+
const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
|
|
13447
|
+
if (Array.isArray(body.logs)) out.push(...body.logs);
|
|
13448
|
+
if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
|
|
13449
|
+
pageStart = body.nextStartTime;
|
|
13450
|
+
pageEnd = body.nextEndTime;
|
|
13451
|
+
}
|
|
13452
|
+
return out;
|
|
13453
|
+
}
|
|
13454
|
+
|
|
13455
|
+
// src/connectors/render/index.ts
|
|
13456
|
+
var ROUTE_TARGET_KIND2 = "route";
|
|
13457
|
+
var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
|
|
13458
|
+
function buildRenderRouteIndex(graph, serviceName) {
|
|
13459
|
+
const out = [];
|
|
13460
|
+
graph.forEachNode((_id, attrs) => {
|
|
13461
|
+
const node = attrs;
|
|
13462
|
+
if (node.type !== import_types64.NodeType.RouteNode) return;
|
|
13463
|
+
const route = attrs;
|
|
13464
|
+
if (route.service !== serviceName) return;
|
|
13465
|
+
out.push({
|
|
13466
|
+
method: route.method.toUpperCase(),
|
|
13467
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
13468
|
+
routeNodeId: route.id,
|
|
13469
|
+
path: route.path,
|
|
13470
|
+
line: route.line
|
|
13471
|
+
});
|
|
13472
|
+
});
|
|
13473
|
+
return out;
|
|
13474
|
+
}
|
|
13475
|
+
function findRenderRoute(entries, method, normalizedPath) {
|
|
13476
|
+
return entries.find(
|
|
13477
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
|
|
13478
|
+
);
|
|
13479
|
+
}
|
|
13480
|
+
function bucketKey3(method, normalizedPath) {
|
|
13481
|
+
return `${method} ${normalizedPath}`;
|
|
13482
|
+
}
|
|
13483
|
+
function isHttpErrorStatus2(status2) {
|
|
13484
|
+
return status2 >= 400;
|
|
13485
|
+
}
|
|
13486
|
+
function upsertBucket2(buckets2, key, isError, timestamp, build) {
|
|
13487
|
+
const existing = buckets2.get(key);
|
|
13488
|
+
if (existing) {
|
|
13489
|
+
existing.callCount += 1;
|
|
13490
|
+
if (isError) existing.errorCount += 1;
|
|
13491
|
+
if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
|
|
13492
|
+
return;
|
|
13493
|
+
}
|
|
13494
|
+
buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
|
|
13495
|
+
}
|
|
13496
|
+
function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
13497
|
+
const buckets2 = /* @__PURE__ */ new Map();
|
|
13498
|
+
if (!Array.isArray(entries)) return [];
|
|
13499
|
+
for (const entry of entries) {
|
|
13500
|
+
if (!entry || typeof entry !== "object") continue;
|
|
13501
|
+
if (typeof entry.timestamp !== "string") continue;
|
|
13502
|
+
const method = renderLabelValue(entry, "method");
|
|
13503
|
+
const rawPath = renderLabelValue(entry, "path");
|
|
13504
|
+
if (typeof method !== "string" || method.length === 0) continue;
|
|
13505
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) continue;
|
|
13506
|
+
const methodUpper = method.toUpperCase();
|
|
13507
|
+
const pathOnly = rawPath.split("?")[0];
|
|
13508
|
+
const normalizedPath = normalizePathTemplate(pathOnly);
|
|
13509
|
+
const statusCode = Number.parseInt(renderLabelValue(entry, "statusCode") ?? "", 10);
|
|
13510
|
+
const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
|
|
13511
|
+
const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
|
|
13512
|
+
if (match) {
|
|
13513
|
+
upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry.timestamp, () => ({
|
|
13514
|
+
targetKind: ROUTE_TARGET_KIND2,
|
|
13515
|
+
targetName: match.routeNodeId,
|
|
13516
|
+
// RouteNode.line is optional in the schema (packages/types/src/
|
|
13517
|
+
// nodes.ts) even though routes.ts always sets it today — skip the
|
|
13518
|
+
// callSite rather than fabricate a line when it's ever absent
|
|
13519
|
+
// (file-awareness.md §6).
|
|
13520
|
+
...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
|
|
13521
|
+
}));
|
|
13522
|
+
} else {
|
|
13523
|
+
upsertBucket2(
|
|
13524
|
+
buckets2,
|
|
13525
|
+
`unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
|
|
13526
|
+
isError,
|
|
13527
|
+
entry.timestamp,
|
|
13528
|
+
() => ({
|
|
13529
|
+
targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
|
|
13530
|
+
targetName: bucketKey3(methodUpper, normalizedPath)
|
|
13531
|
+
})
|
|
13532
|
+
);
|
|
13533
|
+
}
|
|
13534
|
+
}
|
|
13535
|
+
return [...buckets2.values()].map((b) => ({
|
|
13536
|
+
targetKind: b.targetKind,
|
|
13537
|
+
targetName: b.targetName,
|
|
13538
|
+
callCount: b.callCount,
|
|
13539
|
+
errorCount: b.errorCount,
|
|
13540
|
+
lastObservedIso: b.lastObservedIso,
|
|
13541
|
+
...b.callSite ? { callSite: b.callSite } : {}
|
|
13542
|
+
}));
|
|
13543
|
+
}
|
|
13544
|
+
function createRenderResolveTarget(config) {
|
|
13545
|
+
return (signal) => {
|
|
13546
|
+
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
13547
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
13548
|
+
}
|
|
13549
|
+
return null;
|
|
13550
|
+
};
|
|
13551
|
+
}
|
|
13552
|
+
function createRenderConnector(graph, config) {
|
|
13553
|
+
return {
|
|
13554
|
+
provider: "render",
|
|
13555
|
+
async poll(ctx) {
|
|
13556
|
+
const token = readRenderToken(ctx.credentials);
|
|
13557
|
+
const now = /* @__PURE__ */ new Date();
|
|
13558
|
+
const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
|
|
13559
|
+
const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
|
|
13560
|
+
const endTime = now.toISOString();
|
|
13561
|
+
const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
|
|
13562
|
+
const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
|
|
13563
|
+
return mapRenderRequestLogsToSignals(logs, routeIndex);
|
|
13564
|
+
}
|
|
13565
|
+
};
|
|
13566
|
+
}
|
|
13567
|
+
|
|
12874
13568
|
// src/connectors/registry.ts
|
|
12875
13569
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
12876
13570
|
async function authProbe(input) {
|
|
@@ -13046,6 +13740,71 @@ var PROVIDER_DISPATCH = {
|
|
|
13046
13740
|
return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
|
|
13047
13741
|
}
|
|
13048
13742
|
}
|
|
13743
|
+
},
|
|
13744
|
+
"cloud-run": {
|
|
13745
|
+
provider: "cloud-run",
|
|
13746
|
+
// Cloud Run reads both projectId and accessToken from the credential; the
|
|
13747
|
+
// single-string form maps to the secret (the token), and the required-fields
|
|
13748
|
+
// check below catches a projectId that was never supplied.
|
|
13749
|
+
primaryCredentialKey: "accessToken",
|
|
13750
|
+
requiredCredentialFields: ["projectId", "accessToken"],
|
|
13751
|
+
requiredOptionFields: [],
|
|
13752
|
+
build(graph, options) {
|
|
13753
|
+
return createCloudRunConnector(graph, options);
|
|
13754
|
+
},
|
|
13755
|
+
// POST entries:list with pageSize 1 — the exact surface poll() reads, so the
|
|
13756
|
+
// probe checks the actual `logging.logEntries.list` permission the connector
|
|
13757
|
+
// needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
|
|
13758
|
+
// instead check `logging.logs.list`, falsely rejecting a correctly-scoped
|
|
13759
|
+
// custom role that carries only `logging.logEntries.list` (the narrowest
|
|
13760
|
+
// grant docs/connectors/cloud-run.md documents) — the same false-negative
|
|
13761
|
+
// trap Railway's validate avoids by probing its real query. A 2xx means the
|
|
13762
|
+
// token can list log entries; 401/403 means the provider rejected it.
|
|
13763
|
+
validate({ credentials, fetchImpl }) {
|
|
13764
|
+
const projectId = String(credentials.projectId ?? "");
|
|
13765
|
+
return authProbe({
|
|
13766
|
+
provider: "cloud-run",
|
|
13767
|
+
accountKey: projectId || "validate",
|
|
13768
|
+
url: "https://logging.googleapis.com/v2/entries:list",
|
|
13769
|
+
token: String(credentials.accessToken ?? ""),
|
|
13770
|
+
init: {
|
|
13771
|
+
method: "POST",
|
|
13772
|
+
headers: { "Content-Type": "application/json" },
|
|
13773
|
+
body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
|
|
13774
|
+
},
|
|
13775
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
13776
|
+
});
|
|
13777
|
+
}
|
|
13778
|
+
},
|
|
13779
|
+
render: {
|
|
13780
|
+
provider: "render",
|
|
13781
|
+
primaryCredentialKey: "token",
|
|
13782
|
+
requiredCredentialFields: ["token"],
|
|
13783
|
+
requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
|
|
13784
|
+
build(graph, options) {
|
|
13785
|
+
const config = options;
|
|
13786
|
+
return {
|
|
13787
|
+
connector: createRenderConnector(graph, config),
|
|
13788
|
+
resolveTarget: createRenderResolveTarget(config)
|
|
13789
|
+
};
|
|
13790
|
+
},
|
|
13791
|
+
// GET /v1/services?limit=1 — the cheapest read the Render API key
|
|
13792
|
+
// authenticates against (render.com/docs/api). Unlike Railway's GraphQL
|
|
13793
|
+
// gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
|
|
13794
|
+
// 401/403, so authProbe's status-code check is a true verdict here. The
|
|
13795
|
+
// logs query itself also needs an ownerId + resource; `services` needs
|
|
13796
|
+
// neither and still fails 401 on a bad token, so it's the honest probe.
|
|
13797
|
+
validate({ credentials, options, fetchImpl }) {
|
|
13798
|
+
const cfg = options;
|
|
13799
|
+
const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
|
|
13800
|
+
return authProbe({
|
|
13801
|
+
provider: "render",
|
|
13802
|
+
accountKey: cfg.ownerId ?? "validate",
|
|
13803
|
+
url: `${baseUrl}/services?limit=1`,
|
|
13804
|
+
token: String(credentials.token ?? ""),
|
|
13805
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
13806
|
+
});
|
|
13807
|
+
}
|
|
13049
13808
|
}
|
|
13050
13809
|
};
|
|
13051
13810
|
function vercelCredsFrom(credentials) {
|
|
@@ -13355,11 +14114,11 @@ function registerRoutes(scope, ctx) {
|
|
|
13355
14114
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
13356
14115
|
const parsed = [];
|
|
13357
14116
|
for (const c of candidates) {
|
|
13358
|
-
const r =
|
|
14117
|
+
const r = import_types66.DivergenceTypeSchema.safeParse(c);
|
|
13359
14118
|
if (!r.success) {
|
|
13360
14119
|
return reply.code(400).send({
|
|
13361
14120
|
error: `unknown divergence type "${c}"`,
|
|
13362
|
-
allowed:
|
|
14121
|
+
allowed: import_types66.DivergenceTypeSchema.options
|
|
13363
14122
|
});
|
|
13364
14123
|
}
|
|
13365
14124
|
parsed.push(r.data);
|
|
@@ -13668,7 +14427,7 @@ function registerRoutes(scope, ctx) {
|
|
|
13668
14427
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
13669
14428
|
let violations = await log.readAll();
|
|
13670
14429
|
if (req.query.severity) {
|
|
13671
|
-
const sev =
|
|
14430
|
+
const sev = import_types66.PolicySeveritySchema.safeParse(req.query.severity);
|
|
13672
14431
|
if (!sev.success) {
|
|
13673
14432
|
return reply.code(400).send({
|
|
13674
14433
|
error: "invalid severity",
|
|
@@ -13707,7 +14466,7 @@ function registerRoutes(scope, ctx) {
|
|
|
13707
14466
|
scope.post("/policies/check", async (req, reply) => {
|
|
13708
14467
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
13709
14468
|
if (!proj) return;
|
|
13710
|
-
const parsed =
|
|
14469
|
+
const parsed = import_types66.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
13711
14470
|
if (!parsed.success) {
|
|
13712
14471
|
return reply.code(400).send({
|
|
13713
14472
|
error: "invalid /policies/check body",
|