@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/neatd.cjs
CHANGED
|
@@ -8333,6 +8333,55 @@ function columnsFromClassBody(body) {
|
|
|
8333
8333
|
}
|
|
8334
8334
|
return out;
|
|
8335
8335
|
}
|
|
8336
|
+
function foreignKeyParentTable(call) {
|
|
8337
|
+
const fn = call.childForFieldName("function");
|
|
8338
|
+
const t = fn?.text;
|
|
8339
|
+
if (!t) return null;
|
|
8340
|
+
const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
|
|
8341
|
+
if (base !== "ForeignKey") return null;
|
|
8342
|
+
const target = firstPositionalString(call);
|
|
8343
|
+
if (!target) return null;
|
|
8344
|
+
const parts = target.split(".");
|
|
8345
|
+
if (parts.length < 2) return null;
|
|
8346
|
+
return parts[parts.length - 2];
|
|
8347
|
+
}
|
|
8348
|
+
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
8349
|
+
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8350
|
+
const tree = parseSource6(makePyParser4(), file.content);
|
|
8351
|
+
const out = [];
|
|
8352
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8353
|
+
walk3(tree.rootNode, (node) => {
|
|
8354
|
+
if (node.type !== "class_definition") return;
|
|
8355
|
+
const body = node.childForFieldName("body");
|
|
8356
|
+
const nameNode = node.childForFieldName("name");
|
|
8357
|
+
if (!body || !nameNode) return;
|
|
8358
|
+
const explicit = explicitTablename(body);
|
|
8359
|
+
if (explicit === "computed") return;
|
|
8360
|
+
let childTable = null;
|
|
8361
|
+
if (explicit) childTable = explicit.name;
|
|
8362
|
+
else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
|
|
8363
|
+
if (!childTable) return;
|
|
8364
|
+
walk3(body, (n) => {
|
|
8365
|
+
if (n.type !== "call") return;
|
|
8366
|
+
const parentTable = foreignKeyParentTable(n);
|
|
8367
|
+
if (!parentTable) return;
|
|
8368
|
+
const key = `${childTable}->${parentTable}`;
|
|
8369
|
+
if (seen.has(key)) return;
|
|
8370
|
+
seen.add(key);
|
|
8371
|
+
const line = n.startPosition.row + 1;
|
|
8372
|
+
out.push({
|
|
8373
|
+
childTable,
|
|
8374
|
+
parentTable,
|
|
8375
|
+
evidence: {
|
|
8376
|
+
file: import_node_path35.default.relative(serviceDir, file.path),
|
|
8377
|
+
line,
|
|
8378
|
+
snippet: snippet(file.content, line)
|
|
8379
|
+
}
|
|
8380
|
+
});
|
|
8381
|
+
});
|
|
8382
|
+
});
|
|
8383
|
+
return out;
|
|
8384
|
+
}
|
|
8336
8385
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
8337
8386
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8338
8387
|
const tree = parseSource6(makePyParser4(), file.content);
|
|
@@ -8678,6 +8727,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
8678
8727
|
walk6(tree.rootNode);
|
|
8679
8728
|
return out;
|
|
8680
8729
|
}
|
|
8730
|
+
function enclosingVarName(call) {
|
|
8731
|
+
let node = call;
|
|
8732
|
+
while (node?.parent) {
|
|
8733
|
+
const parent = node.parent;
|
|
8734
|
+
if (parent.type === "variable_declarator") {
|
|
8735
|
+
const name = parent.childForFieldName("name");
|
|
8736
|
+
return name?.type === "identifier" ? name.text : null;
|
|
8737
|
+
}
|
|
8738
|
+
if (parent.type === "call_expression" || parent.type === "member_expression") {
|
|
8739
|
+
node = parent;
|
|
8740
|
+
continue;
|
|
8741
|
+
}
|
|
8742
|
+
return null;
|
|
8743
|
+
}
|
|
8744
|
+
return null;
|
|
8745
|
+
}
|
|
8746
|
+
function collectDrizzleTables(root) {
|
|
8747
|
+
const tables = [];
|
|
8748
|
+
const varToTable = /* @__PURE__ */ new Map();
|
|
8749
|
+
const walk6 = (node) => {
|
|
8750
|
+
if (node.type === "call_expression") {
|
|
8751
|
+
const fn = node.childForFieldName("function");
|
|
8752
|
+
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
8753
|
+
const args = node.childForFieldName("arguments");
|
|
8754
|
+
const argNodes = args ? namedChildren3(args) : [];
|
|
8755
|
+
const tableName = stringLiteralText2(argNodes[0] ?? null);
|
|
8756
|
+
const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
|
|
8757
|
+
if (tableName) {
|
|
8758
|
+
tables.push({ tableName, object: obj });
|
|
8759
|
+
const varName = enclosingVarName(node);
|
|
8760
|
+
if (varName) varToTable.set(varName, tableName);
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8765
|
+
};
|
|
8766
|
+
walk6(root);
|
|
8767
|
+
return { tables, varToTable };
|
|
8768
|
+
}
|
|
8769
|
+
function referencesTargetVar(call) {
|
|
8770
|
+
const fn = call.childForFieldName("function");
|
|
8771
|
+
if (fn?.type !== "member_expression") return null;
|
|
8772
|
+
if (fn.childForFieldName("property")?.text !== "references") return null;
|
|
8773
|
+
const args = call.childForFieldName("arguments");
|
|
8774
|
+
const first = args ? namedChildren3(args)[0] ?? null : null;
|
|
8775
|
+
if (first?.type !== "arrow_function") return null;
|
|
8776
|
+
const body = first.childForFieldName("body");
|
|
8777
|
+
if (body?.type !== "member_expression") return null;
|
|
8778
|
+
const obj = body.childForFieldName("object");
|
|
8779
|
+
return obj?.type === "identifier" ? obj.text : null;
|
|
8780
|
+
}
|
|
8781
|
+
function drizzleForeignKeys(file, serviceDir) {
|
|
8782
|
+
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8783
|
+
const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
|
|
8784
|
+
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
8785
|
+
const out = [];
|
|
8786
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8787
|
+
for (const table of tables) {
|
|
8788
|
+
if (!table.object) continue;
|
|
8789
|
+
const walk6 = (node) => {
|
|
8790
|
+
if (node.type === "call_expression") {
|
|
8791
|
+
const targetVar = referencesTargetVar(node);
|
|
8792
|
+
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
8793
|
+
if (parentTable) {
|
|
8794
|
+
const key = `${table.tableName}->${parentTable}`;
|
|
8795
|
+
if (!seen.has(key)) {
|
|
8796
|
+
seen.add(key);
|
|
8797
|
+
const line = node.startPosition.row + 1;
|
|
8798
|
+
out.push({
|
|
8799
|
+
childTable: table.tableName,
|
|
8800
|
+
parentTable,
|
|
8801
|
+
evidence: {
|
|
8802
|
+
file: import_node_path37.default.relative(serviceDir, file.path),
|
|
8803
|
+
line,
|
|
8804
|
+
snippet: snippet(file.content, line)
|
|
8805
|
+
}
|
|
8806
|
+
});
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8810
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8811
|
+
};
|
|
8812
|
+
walk6(table.object);
|
|
8813
|
+
}
|
|
8814
|
+
return out;
|
|
8815
|
+
}
|
|
8681
8816
|
|
|
8682
8817
|
// src/extract/calls/prisma.ts
|
|
8683
8818
|
init_cjs_shims();
|
|
@@ -8806,6 +8941,96 @@ async function prismaColumnEndpoints(serviceDir) {
|
|
|
8806
8941
|
if (!content) return [];
|
|
8807
8942
|
return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
|
|
8808
8943
|
}
|
|
8944
|
+
function buildModelTableMap(lines) {
|
|
8945
|
+
const map = /* @__PURE__ */ new Map();
|
|
8946
|
+
let current = null;
|
|
8947
|
+
let depth = 0;
|
|
8948
|
+
for (const raw of lines) {
|
|
8949
|
+
if (current === null) {
|
|
8950
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
8951
|
+
if (header && raw.includes("{")) {
|
|
8952
|
+
current = { model: header[1], table: header[1] };
|
|
8953
|
+
depth = netBraces(raw);
|
|
8954
|
+
if (depth <= 0) {
|
|
8955
|
+
map.set(current.model, current.table);
|
|
8956
|
+
current = null;
|
|
8957
|
+
}
|
|
8958
|
+
}
|
|
8959
|
+
continue;
|
|
8960
|
+
}
|
|
8961
|
+
depth += netBraces(raw);
|
|
8962
|
+
const trimmed = stripLineComment(raw).trim();
|
|
8963
|
+
if (trimmed.startsWith("@@")) {
|
|
8964
|
+
const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
|
|
8965
|
+
if (m) current.table = m[1];
|
|
8966
|
+
}
|
|
8967
|
+
if (depth <= 0) {
|
|
8968
|
+
map.set(current.model, current.table);
|
|
8969
|
+
current = null;
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
if (current) map.set(current.model, current.table);
|
|
8973
|
+
return map;
|
|
8974
|
+
}
|
|
8975
|
+
function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
8976
|
+
const content = file.content;
|
|
8977
|
+
if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
|
|
8978
|
+
const lines = content.split("\n");
|
|
8979
|
+
const modelToTable = buildModelTableMap(lines);
|
|
8980
|
+
const out = [];
|
|
8981
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8982
|
+
let current = null;
|
|
8983
|
+
let depth = 0;
|
|
8984
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8985
|
+
const raw = lines[i];
|
|
8986
|
+
const lineNo = i + 1;
|
|
8987
|
+
if (current === null) {
|
|
8988
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
8989
|
+
if (header && raw.includes("{")) {
|
|
8990
|
+
current = { table: modelToTable.get(header[1]) ?? header[1] };
|
|
8991
|
+
depth = netBraces(raw);
|
|
8992
|
+
if (depth <= 0) current = null;
|
|
8993
|
+
}
|
|
8994
|
+
continue;
|
|
8995
|
+
}
|
|
8996
|
+
depth += netBraces(raw);
|
|
8997
|
+
const closing = depth <= 0;
|
|
8998
|
+
const trimmed = stripLineComment(raw).trim();
|
|
8999
|
+
if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
|
|
9000
|
+
const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
|
|
9001
|
+
if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
|
|
9002
|
+
const parentTable = modelToTable.get(fm[2]);
|
|
9003
|
+
if (parentTable) {
|
|
9004
|
+
const key = `${current.table}->${parentTable}`;
|
|
9005
|
+
if (!seen.has(key)) {
|
|
9006
|
+
seen.add(key);
|
|
9007
|
+
out.push({
|
|
9008
|
+
childTable: current.table,
|
|
9009
|
+
parentTable,
|
|
9010
|
+
evidence: {
|
|
9011
|
+
file: import_node_path38.default.relative(serviceDir, file.path),
|
|
9012
|
+
line: lineNo,
|
|
9013
|
+
snippet: snippet(content, lineNo)
|
|
9014
|
+
}
|
|
9015
|
+
});
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
9020
|
+
if (closing) current = null;
|
|
9021
|
+
}
|
|
9022
|
+
return out;
|
|
9023
|
+
}
|
|
9024
|
+
async function prismaForeignKeys(serviceDir) {
|
|
9025
|
+
const schemaPath = await findFirst(serviceDir, [
|
|
9026
|
+
import_node_path38.default.join("prisma", "schema.prisma"),
|
|
9027
|
+
"schema.prisma"
|
|
9028
|
+
]);
|
|
9029
|
+
if (!schemaPath) return [];
|
|
9030
|
+
const content = await readIfExists(schemaPath);
|
|
9031
|
+
if (!content) return [];
|
|
9032
|
+
return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
|
|
9033
|
+
}
|
|
8809
9034
|
|
|
8810
9035
|
// src/extract/calls/go.ts
|
|
8811
9036
|
init_cjs_shims();
|
|
@@ -8982,21 +9207,79 @@ async function addCallEdges(graph, services) {
|
|
|
8982
9207
|
};
|
|
8983
9208
|
}
|
|
8984
9209
|
|
|
9210
|
+
// src/extract/table-edges.ts
|
|
9211
|
+
init_cjs_shims();
|
|
9212
|
+
var import_types31 = require("@neat.is/types");
|
|
9213
|
+
async function addTableEdges(graph, services) {
|
|
9214
|
+
let nodesAdded = 0;
|
|
9215
|
+
let edgesAdded = 0;
|
|
9216
|
+
for (const service of services) {
|
|
9217
|
+
const files = await loadSourceFiles(service.dir);
|
|
9218
|
+
const refs = [];
|
|
9219
|
+
for (const file of files) {
|
|
9220
|
+
try {
|
|
9221
|
+
refs.push(...drizzleForeignKeys(file, service.dir));
|
|
9222
|
+
refs.push(...sqlalchemyForeignKeys(file, service.dir));
|
|
9223
|
+
} catch (err) {
|
|
9224
|
+
recordExtractionError("foreign-key extraction", file.path, err);
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
try {
|
|
9228
|
+
refs.push(...await prismaForeignKeys(service.dir));
|
|
9229
|
+
} catch (err) {
|
|
9230
|
+
recordExtractionError("prisma foreign-key extraction", service.dir, err);
|
|
9231
|
+
}
|
|
9232
|
+
for (const ref of refs) {
|
|
9233
|
+
const childId = (0, import_types31.infraId)("sql-table", ref.childTable);
|
|
9234
|
+
const parentId = (0, import_types31.infraId)("sql-table", ref.parentTable);
|
|
9235
|
+
if (childId === parentId) continue;
|
|
9236
|
+
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
9237
|
+
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
9238
|
+
const edgeId = (0, import_types31.extractedEdgeId)(childId, parentId, import_types31.EdgeType.REFERENCES);
|
|
9239
|
+
if (graph.hasEdge(edgeId)) continue;
|
|
9240
|
+
const edge = {
|
|
9241
|
+
id: edgeId,
|
|
9242
|
+
source: childId,
|
|
9243
|
+
target: parentId,
|
|
9244
|
+
type: import_types31.EdgeType.REFERENCES,
|
|
9245
|
+
provenance: import_types31.Provenance.EXTRACTED,
|
|
9246
|
+
confidence: (0, import_types31.confidenceForExtracted)("structural"),
|
|
9247
|
+
evidence: ref.evidence
|
|
9248
|
+
};
|
|
9249
|
+
graph.addEdgeWithKey(edgeId, childId, parentId, edge);
|
|
9250
|
+
edgesAdded++;
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
return { nodesAdded, edgesAdded };
|
|
9254
|
+
}
|
|
9255
|
+
function ensureTableNode(graph, id, name) {
|
|
9256
|
+
if (graph.hasNode(id)) return 0;
|
|
9257
|
+
const node = {
|
|
9258
|
+
id,
|
|
9259
|
+
type: import_types31.NodeType.InfraNode,
|
|
9260
|
+
name,
|
|
9261
|
+
provider: "self",
|
|
9262
|
+
kind: "sql-table"
|
|
9263
|
+
};
|
|
9264
|
+
graph.addNode(id, node);
|
|
9265
|
+
return 1;
|
|
9266
|
+
}
|
|
9267
|
+
|
|
8985
9268
|
// src/extract/infra/index.ts
|
|
8986
9269
|
init_cjs_shims();
|
|
8987
9270
|
|
|
8988
9271
|
// src/extract/infra/docker-compose.ts
|
|
8989
9272
|
init_cjs_shims();
|
|
8990
9273
|
var import_node_path42 = __toESM(require("path"), 1);
|
|
8991
|
-
var
|
|
9274
|
+
var import_types33 = require("@neat.is/types");
|
|
8992
9275
|
|
|
8993
9276
|
// src/extract/infra/shared.ts
|
|
8994
9277
|
init_cjs_shims();
|
|
8995
|
-
var
|
|
9278
|
+
var import_types32 = require("@neat.is/types");
|
|
8996
9279
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
8997
9280
|
return {
|
|
8998
|
-
id: (0,
|
|
8999
|
-
type:
|
|
9281
|
+
id: (0, import_types32.infraId)(kind, name),
|
|
9282
|
+
type: import_types32.NodeType.InfraNode,
|
|
9000
9283
|
name,
|
|
9001
9284
|
provider,
|
|
9002
9285
|
kind,
|
|
@@ -9040,8 +9323,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
9040
9323
|
source: anchorId,
|
|
9041
9324
|
target: node.id,
|
|
9042
9325
|
type: edgeType,
|
|
9043
|
-
provenance:
|
|
9044
|
-
confidence: (0,
|
|
9326
|
+
provenance: import_types32.Provenance.EXTRACTED,
|
|
9327
|
+
confidence: (0, import_types32.confidenceForExtracted)("structural"),
|
|
9045
9328
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9046
9329
|
};
|
|
9047
9330
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9108,15 +9391,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9108
9391
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
9109
9392
|
const targetId = composeNameToNodeId.get(dep);
|
|
9110
9393
|
if (!targetId) continue;
|
|
9111
|
-
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId,
|
|
9394
|
+
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types33.EdgeType.DEPENDS_ON);
|
|
9112
9395
|
if (graph.hasEdge(edgeId)) continue;
|
|
9113
9396
|
const edge = {
|
|
9114
9397
|
id: edgeId,
|
|
9115
9398
|
source: sourceId,
|
|
9116
9399
|
target: targetId,
|
|
9117
|
-
type:
|
|
9118
|
-
provenance:
|
|
9119
|
-
confidence: (0,
|
|
9400
|
+
type: import_types33.EdgeType.DEPENDS_ON,
|
|
9401
|
+
provenance: import_types33.Provenance.EXTRACTED,
|
|
9402
|
+
confidence: (0, import_types33.confidenceForExtracted)("structural"),
|
|
9120
9403
|
evidence: { file: evidenceFile }
|
|
9121
9404
|
};
|
|
9122
9405
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9130,7 +9413,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9130
9413
|
init_cjs_shims();
|
|
9131
9414
|
var import_node_path43 = __toESM(require("path"), 1);
|
|
9132
9415
|
var import_node_fs17 = require("fs");
|
|
9133
|
-
var
|
|
9416
|
+
var import_types34 = require("@neat.is/types");
|
|
9134
9417
|
function readDockerfile(content) {
|
|
9135
9418
|
let image = null;
|
|
9136
9419
|
const ports = [];
|
|
@@ -9189,15 +9472,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9189
9472
|
);
|
|
9190
9473
|
nodesAdded += fn;
|
|
9191
9474
|
edgesAdded += fe;
|
|
9192
|
-
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id,
|
|
9475
|
+
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types34.EdgeType.RUNS_ON);
|
|
9193
9476
|
if (!graph.hasEdge(edgeId)) {
|
|
9194
9477
|
const edge = {
|
|
9195
9478
|
id: edgeId,
|
|
9196
9479
|
source: fileNodeId,
|
|
9197
9480
|
target: node.id,
|
|
9198
|
-
type:
|
|
9199
|
-
provenance:
|
|
9200
|
-
confidence: (0,
|
|
9481
|
+
type: import_types34.EdgeType.RUNS_ON,
|
|
9482
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9483
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9201
9484
|
evidence: {
|
|
9202
9485
|
file: evidenceFile,
|
|
9203
9486
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -9212,15 +9495,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9212
9495
|
graph.addNode(portNode.id, portNode);
|
|
9213
9496
|
nodesAdded++;
|
|
9214
9497
|
}
|
|
9215
|
-
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id,
|
|
9498
|
+
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types34.EdgeType.CONNECTS_TO);
|
|
9216
9499
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
9217
9500
|
const portEdge = {
|
|
9218
9501
|
id: portEdgeId,
|
|
9219
9502
|
source: fileNodeId,
|
|
9220
9503
|
target: portNode.id,
|
|
9221
|
-
type:
|
|
9222
|
-
provenance:
|
|
9223
|
-
confidence: (0,
|
|
9504
|
+
type: import_types34.EdgeType.CONNECTS_TO,
|
|
9505
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9506
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9224
9507
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
9225
9508
|
};
|
|
9226
9509
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -9234,7 +9517,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9234
9517
|
init_cjs_shims();
|
|
9235
9518
|
var import_node_fs18 = require("fs");
|
|
9236
9519
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
9237
|
-
var
|
|
9520
|
+
var import_types35 = require("@neat.is/types");
|
|
9238
9521
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
9239
9522
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
9240
9523
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -9315,16 +9598,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
9315
9598
|
if (!target) continue;
|
|
9316
9599
|
if (seen.has(target.nodeId)) continue;
|
|
9317
9600
|
seen.add(target.nodeId);
|
|
9318
|
-
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
9601
|
+
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types35.EdgeType.DEPENDS_ON);
|
|
9319
9602
|
if (graph.hasEdge(edgeId)) continue;
|
|
9320
9603
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
9321
9604
|
const edge = {
|
|
9322
9605
|
id: edgeId,
|
|
9323
9606
|
source: resource.nodeId,
|
|
9324
9607
|
target: target.nodeId,
|
|
9325
|
-
type:
|
|
9326
|
-
provenance:
|
|
9327
|
-
confidence: (0,
|
|
9608
|
+
type: import_types35.EdgeType.DEPENDS_ON,
|
|
9609
|
+
provenance: import_types35.Provenance.EXTRACTED,
|
|
9610
|
+
confidence: (0, import_types35.confidenceForExtracted)("structural"),
|
|
9328
9611
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
9329
9612
|
};
|
|
9330
9613
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9396,7 +9679,7 @@ init_cjs_shims();
|
|
|
9396
9679
|
var import_node_fs20 = require("fs");
|
|
9397
9680
|
var import_node_path46 = __toESM(require("path"), 1);
|
|
9398
9681
|
var import_smol_toml2 = require("smol-toml");
|
|
9399
|
-
var
|
|
9682
|
+
var import_types36 = require("@neat.is/types");
|
|
9400
9683
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
9401
9684
|
async function readWranglerConfig(dir) {
|
|
9402
9685
|
for (const filename of WRANGLER_FILENAMES) {
|
|
@@ -9444,8 +9727,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
9444
9727
|
source: anchorId,
|
|
9445
9728
|
target: node.id,
|
|
9446
9729
|
type: edgeType,
|
|
9447
|
-
provenance:
|
|
9448
|
-
confidence: (0,
|
|
9730
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9731
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9449
9732
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9450
9733
|
};
|
|
9451
9734
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9506,15 +9789,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9506
9789
|
nodesAdded++;
|
|
9507
9790
|
}
|
|
9508
9791
|
if (runtimeNode.id !== anchorId) {
|
|
9509
|
-
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
9792
|
+
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types36.EdgeType.RUNS_ON);
|
|
9510
9793
|
if (!graph.hasEdge(runsOnId)) {
|
|
9511
9794
|
const edge = {
|
|
9512
9795
|
id: runsOnId,
|
|
9513
9796
|
source: anchorId,
|
|
9514
9797
|
target: runtimeNode.id,
|
|
9515
|
-
type:
|
|
9516
|
-
provenance:
|
|
9517
|
-
confidence: (0,
|
|
9798
|
+
type: import_types36.EdgeType.RUNS_ON,
|
|
9799
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9800
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9518
9801
|
evidence: {
|
|
9519
9802
|
file: evidenceFile,
|
|
9520
9803
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -9528,7 +9811,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9528
9811
|
const result = addResourceEdge(
|
|
9529
9812
|
graph,
|
|
9530
9813
|
anchorId,
|
|
9531
|
-
|
|
9814
|
+
import_types36.EdgeType.CONNECTS_TO,
|
|
9532
9815
|
"cloudflare-route",
|
|
9533
9816
|
route,
|
|
9534
9817
|
evidenceFile,
|
|
@@ -9552,7 +9835,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9552
9835
|
const result = addResourceEdge(
|
|
9553
9836
|
graph,
|
|
9554
9837
|
anchorId,
|
|
9555
|
-
|
|
9838
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9556
9839
|
group.kind,
|
|
9557
9840
|
name,
|
|
9558
9841
|
evidenceFile,
|
|
@@ -9566,7 +9849,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9566
9849
|
const result = addResourceEdge(
|
|
9567
9850
|
graph,
|
|
9568
9851
|
anchorId,
|
|
9569
|
-
|
|
9852
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9570
9853
|
"cloudflare-cron",
|
|
9571
9854
|
cron,
|
|
9572
9855
|
evidenceFile,
|
|
@@ -9579,7 +9862,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9579
9862
|
const result = addResourceEdge(
|
|
9580
9863
|
graph,
|
|
9581
9864
|
anchorId,
|
|
9582
|
-
|
|
9865
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9583
9866
|
"cloudflare-env-var",
|
|
9584
9867
|
varName,
|
|
9585
9868
|
evidenceFile,
|
|
@@ -9592,15 +9875,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9592
9875
|
if (!svc.service) continue;
|
|
9593
9876
|
const target = workerIndex.get(svc.service);
|
|
9594
9877
|
if (target && target.anchorId !== anchorId) {
|
|
9595
|
-
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId,
|
|
9878
|
+
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types36.EdgeType.CALLS);
|
|
9596
9879
|
if (!graph.hasEdge(edgeId)) {
|
|
9597
9880
|
const edge = {
|
|
9598
9881
|
id: edgeId,
|
|
9599
9882
|
source: anchorId,
|
|
9600
9883
|
target: target.anchorId,
|
|
9601
|
-
type:
|
|
9602
|
-
provenance:
|
|
9603
|
-
confidence: (0,
|
|
9884
|
+
type: import_types36.EdgeType.CALLS,
|
|
9885
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9886
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9604
9887
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
9605
9888
|
};
|
|
9606
9889
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9611,7 +9894,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9611
9894
|
const result = addResourceEdge(
|
|
9612
9895
|
graph,
|
|
9613
9896
|
anchorId,
|
|
9614
|
-
|
|
9897
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9615
9898
|
"cloudflare-service-binding",
|
|
9616
9899
|
svc.service,
|
|
9617
9900
|
evidenceFile,
|
|
@@ -9628,7 +9911,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9628
9911
|
init_cjs_shims();
|
|
9629
9912
|
var import_node_fs21 = require("fs");
|
|
9630
9913
|
var import_node_path47 = __toESM(require("path"), 1);
|
|
9631
|
-
var
|
|
9914
|
+
var import_types37 = require("@neat.is/types");
|
|
9632
9915
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
9633
9916
|
async function readVercelConfig(dir) {
|
|
9634
9917
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
@@ -9691,12 +9974,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
9691
9974
|
nodesAdded += result.nodesAdded;
|
|
9692
9975
|
edgesAdded += result.edgesAdded;
|
|
9693
9976
|
};
|
|
9694
|
-
add(
|
|
9695
|
-
for (const cron of config.crons ?? []) add(
|
|
9696
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
9697
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
9977
|
+
add(import_types37.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
9978
|
+
for (const cron of config.crons ?? []) add(import_types37.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
9979
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
9980
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
9698
9981
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
9699
|
-
add(
|
|
9982
|
+
add(import_types37.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
9700
9983
|
}
|
|
9701
9984
|
}
|
|
9702
9985
|
return { nodesAdded, edgesAdded };
|
|
@@ -9707,7 +9990,7 @@ init_cjs_shims();
|
|
|
9707
9990
|
var import_node_fs22 = require("fs");
|
|
9708
9991
|
var import_node_path48 = __toESM(require("path"), 1);
|
|
9709
9992
|
var import_smol_toml3 = require("smol-toml");
|
|
9710
|
-
var
|
|
9993
|
+
var import_types38 = require("@neat.is/types");
|
|
9711
9994
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
9712
9995
|
async function readRailwayConfig(dir) {
|
|
9713
9996
|
for (const filename of RAILWAY_FILENAMES) {
|
|
@@ -9753,9 +10036,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
9753
10036
|
nodesAdded += result.nodesAdded;
|
|
9754
10037
|
edgesAdded += result.edgesAdded;
|
|
9755
10038
|
};
|
|
9756
|
-
add(
|
|
9757
|
-
add(
|
|
9758
|
-
add(
|
|
10039
|
+
add(import_types38.EdgeType.RUNS_ON, "railway", "railway");
|
|
10040
|
+
add(import_types38.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
10041
|
+
add(import_types38.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
9759
10042
|
}
|
|
9760
10043
|
return { nodesAdded, edgesAdded };
|
|
9761
10044
|
}
|
|
@@ -9765,7 +10048,7 @@ init_cjs_shims();
|
|
|
9765
10048
|
var import_node_fs23 = require("fs");
|
|
9766
10049
|
var import_node_path49 = __toESM(require("path"), 1);
|
|
9767
10050
|
var import_smol_toml4 = require("smol-toml");
|
|
9768
|
-
var
|
|
10051
|
+
var import_types39 = require("@neat.is/types");
|
|
9769
10052
|
async function readSupabaseConfig(dir) {
|
|
9770
10053
|
const relFile = import_node_path49.default.join("supabase", "config.toml");
|
|
9771
10054
|
const abs = import_node_path49.default.join(dir, relFile);
|
|
@@ -9813,10 +10096,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
9813
10096
|
nodesAdded += result.nodesAdded;
|
|
9814
10097
|
edgesAdded += result.edgesAdded;
|
|
9815
10098
|
};
|
|
9816
|
-
add(
|
|
9817
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
9818
|
-
if (config.storage) add(
|
|
9819
|
-
if (config.auth) add(
|
|
10099
|
+
add(import_types39.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
10100
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types39.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
10101
|
+
if (config.storage) add(import_types39.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
10102
|
+
if (config.auth) add(import_types39.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
9820
10103
|
}
|
|
9821
10104
|
return { nodesAdded, edgesAdded };
|
|
9822
10105
|
}
|
|
@@ -9844,11 +10127,11 @@ var import_node_path51 = __toESM(require("path"), 1);
|
|
|
9844
10127
|
init_cjs_shims();
|
|
9845
10128
|
var import_node_fs24 = require("fs");
|
|
9846
10129
|
var import_node_path50 = __toESM(require("path"), 1);
|
|
9847
|
-
var
|
|
10130
|
+
var import_types40 = require("@neat.is/types");
|
|
9848
10131
|
function dropOrphanedFileNodes(graph) {
|
|
9849
10132
|
const orphans = [];
|
|
9850
10133
|
graph.forEachNode((id, attrs) => {
|
|
9851
|
-
if (attrs.type !==
|
|
10134
|
+
if (attrs.type !== import_types40.NodeType.FileNode) return;
|
|
9852
10135
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
9853
10136
|
orphans.push(id);
|
|
9854
10137
|
}
|
|
@@ -9861,7 +10144,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
9861
10144
|
const bases = [scanPath, ...serviceDirs];
|
|
9862
10145
|
graph.forEachEdge((id, attrs) => {
|
|
9863
10146
|
const edge = attrs;
|
|
9864
|
-
if (edge.provenance !==
|
|
10147
|
+
if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
|
|
9865
10148
|
const evidenceFile = edge.evidence?.file;
|
|
9866
10149
|
if (!evidenceFile) return;
|
|
9867
10150
|
if (import_node_path50.default.isAbsolute(evidenceFile)) {
|
|
@@ -9892,6 +10175,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9892
10175
|
const routePhase = await addRoutes(graph, services);
|
|
9893
10176
|
const grpcPhase = await addGrpcMethods(graph, services);
|
|
9894
10177
|
const phase4 = await addCallEdges(graph, services);
|
|
10178
|
+
const tableEdges = await addTableEdges(graph, services);
|
|
9895
10179
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
9896
10180
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
9897
10181
|
graph,
|
|
@@ -9931,8 +10215,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9931
10215
|
}
|
|
9932
10216
|
}
|
|
9933
10217
|
const result = {
|
|
9934
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
9935
|
-
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
10218
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
|
|
10219
|
+
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
|
|
9936
10220
|
frontiersPromoted,
|
|
9937
10221
|
extractionErrors: errorEntries.length,
|
|
9938
10222
|
errorEntries,
|
|
@@ -9957,7 +10241,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9957
10241
|
init_cjs_shims();
|
|
9958
10242
|
var import_node_fs25 = require("fs");
|
|
9959
10243
|
var import_node_path52 = __toESM(require("path"), 1);
|
|
9960
|
-
var
|
|
10244
|
+
var import_types41 = require("@neat.is/types");
|
|
9961
10245
|
var SCHEMA_VERSION = 6;
|
|
9962
10246
|
function migrateV1ToV2(payload) {
|
|
9963
10247
|
const nodes = payload.graph.nodes;
|
|
@@ -9981,7 +10265,7 @@ function migrateV5ToV6(payload) {
|
|
|
9981
10265
|
if (Array.isArray(nodes)) {
|
|
9982
10266
|
for (const node of nodes) {
|
|
9983
10267
|
const attrs = node.attributes;
|
|
9984
|
-
if (!attrs || attrs.type !==
|
|
10268
|
+
if (!attrs || attrs.type !== import_types41.NodeType.InfraNode) continue;
|
|
9985
10269
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
9986
10270
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
9987
10271
|
}
|
|
@@ -9994,12 +10278,12 @@ function migrateV2ToV3(payload) {
|
|
|
9994
10278
|
for (const edge of edges) {
|
|
9995
10279
|
const attrs = edge.attributes;
|
|
9996
10280
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
9997
|
-
attrs.provenance =
|
|
10281
|
+
attrs.provenance = import_types41.Provenance.OBSERVED;
|
|
9998
10282
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
9999
10283
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
10000
10284
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
10001
10285
|
if (type && source && target) {
|
|
10002
|
-
const newId = (0,
|
|
10286
|
+
const newId = (0, import_types41.observedEdgeId)(source, target, type);
|
|
10003
10287
|
attrs.id = newId;
|
|
10004
10288
|
if (edge.key) edge.key = newId;
|
|
10005
10289
|
}
|
|
@@ -10149,7 +10433,7 @@ var Projects = class {
|
|
|
10149
10433
|
init_cjs_shims();
|
|
10150
10434
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
10151
10435
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
10152
|
-
var
|
|
10436
|
+
var import_types66 = require("@neat.is/types");
|
|
10153
10437
|
|
|
10154
10438
|
// src/extend/index.ts
|
|
10155
10439
|
init_cjs_shims();
|
|
@@ -10496,39 +10780,39 @@ async function rollbackExtension(ctx, args) {
|
|
|
10496
10780
|
|
|
10497
10781
|
// src/divergences.ts
|
|
10498
10782
|
init_cjs_shims();
|
|
10499
|
-
var
|
|
10783
|
+
var import_types42 = require("@neat.is/types");
|
|
10500
10784
|
function bucketKey(source, target, type) {
|
|
10501
10785
|
return `${type}|${source}|${target}`;
|
|
10502
10786
|
}
|
|
10503
10787
|
function bucketSourceFor(graph, edge) {
|
|
10504
|
-
if (edge.type !==
|
|
10505
|
-
const parsed = (0,
|
|
10788
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) return edge.source;
|
|
10789
|
+
const parsed = (0, import_types42.parseFileId)(edge.source);
|
|
10506
10790
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
10507
10791
|
const target = graph.getNodeAttributes(edge.target);
|
|
10508
|
-
if (target.type !==
|
|
10509
|
-
return (0,
|
|
10792
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) return edge.source;
|
|
10793
|
+
return (0, import_types42.serviceId)(parsed.service);
|
|
10510
10794
|
}
|
|
10511
10795
|
function bucketEdges(graph) {
|
|
10512
10796
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10513
10797
|
graph.forEachEdge((id, attrs) => {
|
|
10514
10798
|
const e = attrs;
|
|
10515
|
-
const parsed = (0,
|
|
10799
|
+
const parsed = (0, import_types42.parseEdgeId)(id);
|
|
10516
10800
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
10517
10801
|
const source = bucketSourceFor(graph, e);
|
|
10518
10802
|
const key = bucketKey(source, e.target, e.type);
|
|
10519
10803
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
10520
10804
|
switch (provenance) {
|
|
10521
|
-
case
|
|
10805
|
+
case import_types42.Provenance.EXTRACTED:
|
|
10522
10806
|
cur.extracted = e;
|
|
10523
10807
|
break;
|
|
10524
|
-
case
|
|
10808
|
+
case import_types42.Provenance.OBSERVED:
|
|
10525
10809
|
cur.observed = e;
|
|
10526
10810
|
break;
|
|
10527
|
-
case
|
|
10811
|
+
case import_types42.Provenance.INFERRED:
|
|
10528
10812
|
cur.inferred = e;
|
|
10529
10813
|
break;
|
|
10530
10814
|
default:
|
|
10531
|
-
if (e.provenance ===
|
|
10815
|
+
if (e.provenance === import_types42.Provenance.STALE) cur.stale = e;
|
|
10532
10816
|
}
|
|
10533
10817
|
buckets2.set(key, cur);
|
|
10534
10818
|
});
|
|
@@ -10537,17 +10821,17 @@ function bucketEdges(graph) {
|
|
|
10537
10821
|
function nodeIsFrontier(graph, nodeId) {
|
|
10538
10822
|
if (!graph.hasNode(nodeId)) return false;
|
|
10539
10823
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10540
|
-
return attrs.type ===
|
|
10824
|
+
return attrs.type === import_types42.NodeType.FrontierNode;
|
|
10541
10825
|
}
|
|
10542
10826
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
10543
10827
|
if (!graph.hasNode(nodeId)) return false;
|
|
10544
10828
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10545
|
-
return attrs.type ===
|
|
10829
|
+
return attrs.type === import_types42.NodeType.WebSocketChannelNode;
|
|
10546
10830
|
}
|
|
10547
10831
|
function nodeIsSymbol(graph, nodeId) {
|
|
10548
10832
|
if (!graph.hasNode(nodeId)) return false;
|
|
10549
10833
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10550
|
-
return attrs.type ===
|
|
10834
|
+
return attrs.type === import_types42.NodeType.SymbolNode;
|
|
10551
10835
|
}
|
|
10552
10836
|
function clampConfidence(n) {
|
|
10553
10837
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -10567,14 +10851,14 @@ function gradedConfidence(edge) {
|
|
|
10567
10851
|
return clampConfidence(confidenceForEdge(edge));
|
|
10568
10852
|
}
|
|
10569
10853
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10854
|
+
import_types42.EdgeType.CALLS,
|
|
10855
|
+
import_types42.EdgeType.CONNECTS_TO,
|
|
10856
|
+
import_types42.EdgeType.PUBLISHES_TO,
|
|
10857
|
+
import_types42.EdgeType.CONSUMES_FROM
|
|
10574
10858
|
]);
|
|
10575
10859
|
function detectMissingDivergences(graph, bucket) {
|
|
10576
10860
|
const out = [];
|
|
10577
|
-
if (bucket.type ===
|
|
10861
|
+
if (bucket.type === import_types42.EdgeType.CONTAINS) return out;
|
|
10578
10862
|
if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
|
|
10579
10863
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
10580
10864
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
@@ -10616,7 +10900,7 @@ function declaredHostFor(svc) {
|
|
|
10616
10900
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
10617
10901
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10618
10902
|
const e = graph.getEdgeAttributes(edgeId);
|
|
10619
|
-
if (e.type ===
|
|
10903
|
+
if (e.type === import_types42.EdgeType.CONFIGURED_BY && e.provenance === import_types42.Provenance.EXTRACTED) {
|
|
10620
10904
|
return true;
|
|
10621
10905
|
}
|
|
10622
10906
|
}
|
|
@@ -10629,10 +10913,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
10629
10913
|
const out = [];
|
|
10630
10914
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10631
10915
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10632
|
-
if (edge.type !==
|
|
10633
|
-
if (edge.provenance !==
|
|
10916
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) continue;
|
|
10917
|
+
if (edge.provenance !== import_types42.Provenance.OBSERVED) continue;
|
|
10634
10918
|
const target = graph.getNodeAttributes(edge.target);
|
|
10635
|
-
if (target.type !==
|
|
10919
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) continue;
|
|
10636
10920
|
const observedHost = target.host?.trim();
|
|
10637
10921
|
if (!observedHost) continue;
|
|
10638
10922
|
if (observedHost === declaredHost) continue;
|
|
@@ -10654,10 +10938,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
10654
10938
|
const deps = svc.dependencies ?? {};
|
|
10655
10939
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10656
10940
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10657
|
-
if (edge.type !==
|
|
10658
|
-
if (edge.provenance !==
|
|
10941
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) continue;
|
|
10942
|
+
if (edge.provenance !== import_types42.Provenance.OBSERVED) continue;
|
|
10659
10943
|
const target = graph.getNodeAttributes(edge.target);
|
|
10660
|
-
if (target.type !==
|
|
10944
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) continue;
|
|
10661
10945
|
for (const pair of compatPairs()) {
|
|
10662
10946
|
if (pair.engine !== target.engine) continue;
|
|
10663
10947
|
const declared = deps[pair.driver];
|
|
@@ -10754,7 +11038,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
10754
11038
|
for (const d of all) {
|
|
10755
11039
|
if (d.type !== "host-mismatch") continue;
|
|
10756
11040
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
10757
|
-
declaredHalf.add((0,
|
|
11041
|
+
declaredHalf.add((0, import_types42.databaseId)(d.extractedHost));
|
|
10758
11042
|
}
|
|
10759
11043
|
if (observedHalf.size === 0) return all;
|
|
10760
11044
|
return all.filter((d) => {
|
|
@@ -10773,13 +11057,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10773
11057
|
}
|
|
10774
11058
|
graph.forEachNode((nodeId, attrs) => {
|
|
10775
11059
|
const n = attrs;
|
|
10776
|
-
if (n.type ===
|
|
11060
|
+
if (n.type === import_types42.NodeType.ServiceNode) {
|
|
10777
11061
|
const svc = n;
|
|
10778
11062
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
10779
11063
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
10780
11064
|
return;
|
|
10781
11065
|
}
|
|
10782
|
-
if (n.type ===
|
|
11066
|
+
if (n.type === import_types42.NodeType.InfraNode && n.kind === "sql-table") {
|
|
10783
11067
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
10784
11068
|
}
|
|
10785
11069
|
});
|
|
@@ -10815,7 +11099,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10815
11099
|
const bc = "column" in b && b.column ? b.column : "";
|
|
10816
11100
|
return ac.localeCompare(bc);
|
|
10817
11101
|
});
|
|
10818
|
-
return
|
|
11102
|
+
return import_types42.DivergenceResultSchema.parse({
|
|
10819
11103
|
divergences: filtered,
|
|
10820
11104
|
totalAffected: filtered.length,
|
|
10821
11105
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -10949,7 +11233,7 @@ init_cjs_shims();
|
|
|
10949
11233
|
var import_node_fs29 = require("fs");
|
|
10950
11234
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
10951
11235
|
var import_node_path56 = __toESM(require("path"), 1);
|
|
10952
|
-
var
|
|
11236
|
+
var import_types43 = require("@neat.is/types");
|
|
10953
11237
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
10954
11238
|
var LOCK_RETRY_MS = 50;
|
|
10955
11239
|
function neatHome() {
|
|
@@ -11151,10 +11435,10 @@ async function readRegistry() {
|
|
|
11151
11435
|
throw err;
|
|
11152
11436
|
}
|
|
11153
11437
|
const parsed = JSON.parse(raw);
|
|
11154
|
-
return
|
|
11438
|
+
return import_types43.RegistryFileSchema.parse(parsed);
|
|
11155
11439
|
}
|
|
11156
11440
|
async function writeRegistry(reg) {
|
|
11157
|
-
const validated =
|
|
11441
|
+
const validated = import_types43.RegistryFileSchema.parse(reg);
|
|
11158
11442
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
11159
11443
|
}
|
|
11160
11444
|
async function getProject(name) {
|
|
@@ -11505,15 +11789,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
11505
11789
|
|
|
11506
11790
|
// src/connectors/index.ts
|
|
11507
11791
|
init_cjs_shims();
|
|
11508
|
-
var
|
|
11792
|
+
var import_types44 = require("@neat.is/types");
|
|
11509
11793
|
var NO_ENV = "unknown";
|
|
11510
11794
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
11511
11795
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11512
11796
|
const sites = [];
|
|
11513
11797
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
11514
11798
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
11515
|
-
if (edge.provenance !==
|
|
11516
|
-
const parsed = (0,
|
|
11799
|
+
if (edge.provenance !== import_types44.Provenance.EXTRACTED) continue;
|
|
11800
|
+
const parsed = (0, import_types44.parseFileId)(edge.source);
|
|
11517
11801
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
11518
11802
|
const site = { relPath: edge.evidence.file };
|
|
11519
11803
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -11524,7 +11808,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
11524
11808
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
11525
11809
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11526
11810
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
11527
|
-
if (attrs.type !==
|
|
11811
|
+
if (attrs.type !== import_types44.NodeType.RouteNode || !attrs.path) return void 0;
|
|
11528
11812
|
const site = { relPath: attrs.path };
|
|
11529
11813
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
11530
11814
|
return site;
|
|
@@ -11708,7 +11992,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
|
|
|
11708
11992
|
// connector add/remove/test` (provision/deprovision/validate), never a poll
|
|
11709
11993
|
// loop, so this bucket is exercised a handful of times per command. Kept
|
|
11710
11994
|
// conservative pending a documented Drains-API rate limit.
|
|
11711
|
-
vercel: { capacity: 20, refillMs: 5e3 }
|
|
11995
|
+
vercel: { capacity: 20, refillMs: 5e3 },
|
|
11996
|
+
// Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
|
|
11997
|
+
// pinned here to a single confirmed number — this is a conservative
|
|
11998
|
+
// placeholder pending a live project, matching the other pull providers.
|
|
11999
|
+
render: { capacity: 30, refillMs: 1e4 }
|
|
11712
12000
|
};
|
|
11713
12001
|
var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
|
|
11714
12002
|
function defaultRateLimitFor(provider) {
|
|
@@ -12115,23 +12403,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
12115
12403
|
|
|
12116
12404
|
// src/connectors/supabase/resolve.ts
|
|
12117
12405
|
init_cjs_shims();
|
|
12118
|
-
var
|
|
12406
|
+
var import_types46 = require("@neat.is/types");
|
|
12119
12407
|
function createSupabaseResolveTarget(graph, config) {
|
|
12120
12408
|
return (signal, _ctx) => {
|
|
12121
12409
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
12122
12410
|
return null;
|
|
12123
12411
|
}
|
|
12124
|
-
const subResourceId = (0,
|
|
12412
|
+
const subResourceId = (0, import_types46.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
12125
12413
|
if (graph.hasNode(subResourceId)) {
|
|
12126
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
12414
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12127
12415
|
}
|
|
12128
|
-
const bareResourceId = (0,
|
|
12416
|
+
const bareResourceId = (0, import_types46.infraId)(signal.targetKind, signal.targetName);
|
|
12129
12417
|
if (graph.hasNode(bareResourceId)) {
|
|
12130
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
12418
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12131
12419
|
}
|
|
12132
|
-
const projectLevelId = (0,
|
|
12420
|
+
const projectLevelId = (0, import_types46.infraId)("supabase", config.nodeRef);
|
|
12133
12421
|
if (graph.hasNode(projectLevelId)) {
|
|
12134
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
12422
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12135
12423
|
}
|
|
12136
12424
|
return null;
|
|
12137
12425
|
};
|
|
@@ -12224,7 +12512,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
12224
12512
|
|
|
12225
12513
|
// src/connectors/railway/index.ts
|
|
12226
12514
|
init_cjs_shims();
|
|
12227
|
-
var
|
|
12515
|
+
var import_types50 = require("@neat.is/types");
|
|
12228
12516
|
|
|
12229
12517
|
// src/connectors/railway/client.ts
|
|
12230
12518
|
init_cjs_shims();
|
|
@@ -12375,7 +12663,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
12375
12663
|
const out = [];
|
|
12376
12664
|
graph.forEachNode((_id, attrs) => {
|
|
12377
12665
|
const node = attrs;
|
|
12378
|
-
if (node.type !==
|
|
12666
|
+
if (node.type !== import_types50.NodeType.RouteNode) return;
|
|
12379
12667
|
const route = attrs;
|
|
12380
12668
|
if (route.service !== serviceName) return;
|
|
12381
12669
|
out.push({
|
|
@@ -12479,12 +12767,12 @@ function createRailwayResolveTarget(config) {
|
|
|
12479
12767
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
12480
12768
|
if (!serviceName) return null;
|
|
12481
12769
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
12482
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
12770
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types50.EdgeType.CALLS };
|
|
12483
12771
|
}
|
|
12484
12772
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
12485
12773
|
const peerName = config.serviceNameById[signal.targetName];
|
|
12486
12774
|
if (!peerName) return null;
|
|
12487
|
-
return { targetNodeId: (0,
|
|
12775
|
+
return { targetNodeId: (0, import_types50.serviceId)(peerName), serviceName, edgeType: import_types50.EdgeType.CONNECTS_TO };
|
|
12488
12776
|
}
|
|
12489
12777
|
return null;
|
|
12490
12778
|
};
|
|
@@ -12672,7 +12960,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
12672
12960
|
|
|
12673
12961
|
// src/connectors/firebase/resolve.ts
|
|
12674
12962
|
init_cjs_shims();
|
|
12675
|
-
var
|
|
12963
|
+
var import_types51 = require("@neat.is/types");
|
|
12676
12964
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
12677
12965
|
switch (resourceType) {
|
|
12678
12966
|
case "cloud_function":
|
|
@@ -12687,7 +12975,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
12687
12975
|
const entries = [];
|
|
12688
12976
|
graph.forEachNode((_id, attrs) => {
|
|
12689
12977
|
const node = attrs;
|
|
12690
|
-
if (node.type !==
|
|
12978
|
+
if (node.type !== import_types51.NodeType.RouteNode) return;
|
|
12691
12979
|
const route = attrs;
|
|
12692
12980
|
if (route.service !== serviceName) return;
|
|
12693
12981
|
entries.push({
|
|
@@ -12719,7 +13007,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
12719
13007
|
return {
|
|
12720
13008
|
targetNodeId: match.routeNodeId,
|
|
12721
13009
|
serviceName,
|
|
12722
|
-
edgeType:
|
|
13010
|
+
edgeType: import_types51.EdgeType.CALLS
|
|
12723
13011
|
};
|
|
12724
13012
|
};
|
|
12725
13013
|
}
|
|
@@ -12746,7 +13034,7 @@ init_cjs_shims();
|
|
|
12746
13034
|
|
|
12747
13035
|
// src/connectors/cloudflare/connector.ts
|
|
12748
13036
|
init_cjs_shims();
|
|
12749
|
-
var
|
|
13037
|
+
var import_types53 = require("@neat.is/types");
|
|
12750
13038
|
|
|
12751
13039
|
// src/connectors/cloudflare/client.ts
|
|
12752
13040
|
init_cjs_shims();
|
|
@@ -12910,7 +13198,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
12910
13198
|
graph.forEachNode((id, attrs) => {
|
|
12911
13199
|
if (found) return;
|
|
12912
13200
|
const a = attrs;
|
|
12913
|
-
if (a.type ===
|
|
13201
|
+
if (a.type === import_types53.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
12914
13202
|
found = id;
|
|
12915
13203
|
}
|
|
12916
13204
|
});
|
|
@@ -12922,7 +13210,7 @@ function findMatchingRouteNode(graph, serviceName, method, path62) {
|
|
|
12922
13210
|
graph.forEachNode((id, attrs) => {
|
|
12923
13211
|
if (found) return;
|
|
12924
13212
|
const a = attrs;
|
|
12925
|
-
if (a.type !==
|
|
13213
|
+
if (a.type !== import_types53.NodeType.RouteNode || a.service !== serviceName) return;
|
|
12926
13214
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
12927
13215
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
12928
13216
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -12941,11 +13229,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
12941
13229
|
};
|
|
12942
13230
|
const mapping = config.workers?.[scriptName];
|
|
12943
13231
|
if (mapping) {
|
|
12944
|
-
const wholeFileId = (0,
|
|
13232
|
+
const wholeFileId = (0, import_types53.fileId)(mapping.service, mapping.entryFile);
|
|
12945
13233
|
return {
|
|
12946
13234
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
12947
13235
|
serviceName: mapping.service,
|
|
12948
|
-
edgeType:
|
|
13236
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
12949
13237
|
};
|
|
12950
13238
|
}
|
|
12951
13239
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -12954,13 +13242,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
12954
13242
|
return {
|
|
12955
13243
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
12956
13244
|
serviceName: fileNode.service,
|
|
12957
|
-
edgeType:
|
|
13245
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
12958
13246
|
};
|
|
12959
13247
|
}
|
|
12960
13248
|
return {
|
|
12961
|
-
targetNodeId: (0,
|
|
13249
|
+
targetNodeId: (0, import_types53.infraId)("cloudflare-worker", scriptName),
|
|
12962
13250
|
serviceName: scriptName,
|
|
12963
|
-
edgeType:
|
|
13251
|
+
edgeType: import_types53.EdgeType.CALLS,
|
|
12964
13252
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
12965
13253
|
};
|
|
12966
13254
|
};
|
|
@@ -13156,14 +13444,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
13156
13444
|
|
|
13157
13445
|
// src/connectors/neon/resolve.ts
|
|
13158
13446
|
init_cjs_shims();
|
|
13159
|
-
var
|
|
13447
|
+
var import_types57 = require("@neat.is/types");
|
|
13160
13448
|
function createNeonResolveTarget(config) {
|
|
13161
13449
|
return (signal) => {
|
|
13162
13450
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
13163
13451
|
return {
|
|
13164
|
-
targetNodeId: (0,
|
|
13452
|
+
targetNodeId: (0, import_types57.infraId)("sql-table", signal.targetName),
|
|
13165
13453
|
serviceName: config.serviceName,
|
|
13166
|
-
edgeType:
|
|
13454
|
+
edgeType: import_types57.EdgeType.CALLS,
|
|
13167
13455
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
13168
13456
|
};
|
|
13169
13457
|
};
|
|
@@ -13201,6 +13489,412 @@ function createNeonConnector(config, deps = {}) {
|
|
|
13201
13489
|
};
|
|
13202
13490
|
}
|
|
13203
13491
|
|
|
13492
|
+
// src/connectors/cloud-run/index.ts
|
|
13493
|
+
init_cjs_shims();
|
|
13494
|
+
|
|
13495
|
+
// src/connectors/cloud-run/client.ts
|
|
13496
|
+
init_cjs_shims();
|
|
13497
|
+
function cloudRunRequestLogName(projectId) {
|
|
13498
|
+
return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
|
|
13499
|
+
}
|
|
13500
|
+
function buildCloudRunEntriesFilter(projectId, sinceIso) {
|
|
13501
|
+
return [
|
|
13502
|
+
`logName = "${cloudRunRequestLogName(projectId)}"`,
|
|
13503
|
+
`resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
|
|
13504
|
+
'httpRequest.requestMethod != ""',
|
|
13505
|
+
`timestamp >= "${sinceIso}"`
|
|
13506
|
+
].join(" AND ");
|
|
13507
|
+
}
|
|
13508
|
+
var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
|
|
13509
|
+
var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
|
|
13510
|
+
var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
|
|
13511
|
+
var PAGE_SIZE2 = 1e3;
|
|
13512
|
+
var MAX_PAGES2 = 20;
|
|
13513
|
+
async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
|
|
13514
|
+
const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
|
|
13515
|
+
const out = [];
|
|
13516
|
+
let pageToken;
|
|
13517
|
+
for (let page = 0; page < MAX_PAGES2; page++) {
|
|
13518
|
+
const body = {
|
|
13519
|
+
resourceNames: [`projects/${creds.projectId}`],
|
|
13520
|
+
filter,
|
|
13521
|
+
orderBy: "timestamp asc",
|
|
13522
|
+
pageSize: PAGE_SIZE2,
|
|
13523
|
+
...pageToken ? { pageToken } : {}
|
|
13524
|
+
};
|
|
13525
|
+
const res = await junctionFetch(
|
|
13526
|
+
apiUrl,
|
|
13527
|
+
{
|
|
13528
|
+
method: "POST",
|
|
13529
|
+
headers: {
|
|
13530
|
+
...bearerAuthHeader(creds.accessToken),
|
|
13531
|
+
"Content-Type": "application/json"
|
|
13532
|
+
},
|
|
13533
|
+
body: JSON.stringify(body)
|
|
13534
|
+
},
|
|
13535
|
+
// accountKey: the GCP project id — one customer's Cloud Logging quota is
|
|
13536
|
+
// scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
|
|
13537
|
+
// bucket), the same key Firebase's connector uses.
|
|
13538
|
+
{ provider: "cloud-run", accountKey: creds.projectId }
|
|
13539
|
+
);
|
|
13540
|
+
if (!res.ok) {
|
|
13541
|
+
throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
|
|
13542
|
+
}
|
|
13543
|
+
const json = await res.json();
|
|
13544
|
+
if (Array.isArray(json.entries)) out.push(...json.entries);
|
|
13545
|
+
if (!json.nextPageToken) break;
|
|
13546
|
+
pageToken = json.nextPageToken;
|
|
13547
|
+
}
|
|
13548
|
+
return out;
|
|
13549
|
+
}
|
|
13550
|
+
|
|
13551
|
+
// src/connectors/cloud-run/map.ts
|
|
13552
|
+
init_cjs_shims();
|
|
13553
|
+
|
|
13554
|
+
// src/connectors/cloud-run/types.ts
|
|
13555
|
+
init_cjs_shims();
|
|
13556
|
+
function readCloudRunCredentials(raw) {
|
|
13557
|
+
const projectId = raw["projectId"];
|
|
13558
|
+
const accessToken = raw["accessToken"];
|
|
13559
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
13560
|
+
throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
|
|
13561
|
+
}
|
|
13562
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
13563
|
+
throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
|
|
13564
|
+
}
|
|
13565
|
+
return { projectId, accessToken };
|
|
13566
|
+
}
|
|
13567
|
+
var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
|
|
13568
|
+
var FIELD_SEP2 = "\0";
|
|
13569
|
+
function packCloudRunTargetName(identity) {
|
|
13570
|
+
return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
|
|
13571
|
+
}
|
|
13572
|
+
function parseCloudRunTargetName(targetName) {
|
|
13573
|
+
const firstSep = targetName.indexOf(FIELD_SEP2);
|
|
13574
|
+
if (firstSep === -1) return null;
|
|
13575
|
+
const serviceName = targetName.slice(0, firstSep);
|
|
13576
|
+
const rest = targetName.slice(firstSep + 1);
|
|
13577
|
+
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
13578
|
+
if (secondSep === -1) return null;
|
|
13579
|
+
const method = rest.slice(0, secondSep);
|
|
13580
|
+
const path62 = rest.slice(secondSep + 1);
|
|
13581
|
+
if (!serviceName || !method || !path62) return null;
|
|
13582
|
+
return { serviceName, method, path: path62 };
|
|
13583
|
+
}
|
|
13584
|
+
|
|
13585
|
+
// src/connectors/cloud-run/map.ts
|
|
13586
|
+
var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
|
|
13587
|
+
function pathFromRequestUrl2(requestUrl) {
|
|
13588
|
+
if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
|
|
13589
|
+
if (requestUrl.startsWith("/")) {
|
|
13590
|
+
const withoutQuery = requestUrl.split("?")[0];
|
|
13591
|
+
return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
|
|
13592
|
+
}
|
|
13593
|
+
try {
|
|
13594
|
+
const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
|
|
13595
|
+
const parsed = new URL(candidate);
|
|
13596
|
+
return parsed.pathname || "/";
|
|
13597
|
+
} catch {
|
|
13598
|
+
return null;
|
|
13599
|
+
}
|
|
13600
|
+
}
|
|
13601
|
+
var ERROR_STATUS_THRESHOLD4 = 500;
|
|
13602
|
+
function mapLogEntryToSignal2(entry2) {
|
|
13603
|
+
if (!entry2 || typeof entry2 !== "object") return null;
|
|
13604
|
+
if (entry2.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
|
|
13605
|
+
const serviceName = entry2.resource?.labels?.["service_name"];
|
|
13606
|
+
if (typeof serviceName !== "string" || serviceName.length === 0) return null;
|
|
13607
|
+
const req2 = entry2.httpRequest;
|
|
13608
|
+
if (!req2) return null;
|
|
13609
|
+
if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
|
|
13610
|
+
const method = req2.requestMethod.toUpperCase();
|
|
13611
|
+
const path62 = pathFromRequestUrl2(req2.requestUrl);
|
|
13612
|
+
if (path62 === null) return null;
|
|
13613
|
+
const timestamp = entry2.timestamp;
|
|
13614
|
+
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
13615
|
+
const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD4;
|
|
13616
|
+
return {
|
|
13617
|
+
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
13618
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path62 }),
|
|
13619
|
+
callCount: 1,
|
|
13620
|
+
errorCount: isError ? 1 : 0,
|
|
13621
|
+
lastObservedIso: timestamp
|
|
13622
|
+
};
|
|
13623
|
+
}
|
|
13624
|
+
function mapLogEntriesToSignals2(entries) {
|
|
13625
|
+
const out = [];
|
|
13626
|
+
for (const entry2 of entries) {
|
|
13627
|
+
const signal = mapLogEntryToSignal2(entry2);
|
|
13628
|
+
if (signal) out.push(signal);
|
|
13629
|
+
}
|
|
13630
|
+
return out;
|
|
13631
|
+
}
|
|
13632
|
+
|
|
13633
|
+
// src/connectors/cloud-run/resolve.ts
|
|
13634
|
+
init_cjs_shims();
|
|
13635
|
+
var import_types61 = require("@neat.is/types");
|
|
13636
|
+
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
13637
|
+
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
13638
|
+
let found = null;
|
|
13639
|
+
graph.forEachNode((_id, attrs) => {
|
|
13640
|
+
if (found) return;
|
|
13641
|
+
const node = attrs;
|
|
13642
|
+
if (node.type !== import_types61.NodeType.RouteNode) return;
|
|
13643
|
+
const route = attrs;
|
|
13644
|
+
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
13645
|
+
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
13646
|
+
const routeMethod = route.method.toUpperCase();
|
|
13647
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
13648
|
+
found = route.id;
|
|
13649
|
+
});
|
|
13650
|
+
return found;
|
|
13651
|
+
}
|
|
13652
|
+
function createCloudRunResolveTarget(graph, config) {
|
|
13653
|
+
return (signal) => {
|
|
13654
|
+
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
13655
|
+
const identity = parseCloudRunTargetName(signal.targetName);
|
|
13656
|
+
if (!identity) return null;
|
|
13657
|
+
const { serviceName: gcpServiceName, method, path: path62 } = identity;
|
|
13658
|
+
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
13659
|
+
if (mappedService) {
|
|
13660
|
+
const routeNodeId = findMatchingRouteNode2(
|
|
13661
|
+
graph,
|
|
13662
|
+
mappedService,
|
|
13663
|
+
method,
|
|
13664
|
+
normalizePathTemplate(path62)
|
|
13665
|
+
);
|
|
13666
|
+
if (routeNodeId) {
|
|
13667
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types61.EdgeType.CALLS };
|
|
13668
|
+
}
|
|
13669
|
+
}
|
|
13670
|
+
return {
|
|
13671
|
+
targetNodeId: (0, import_types61.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
13672
|
+
serviceName: mappedService ?? gcpServiceName,
|
|
13673
|
+
edgeType: import_types61.EdgeType.CALLS,
|
|
13674
|
+
ensureInfraNode: {
|
|
13675
|
+
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
13676
|
+
name: gcpServiceName,
|
|
13677
|
+
provider: "cloud-run"
|
|
13678
|
+
}
|
|
13679
|
+
};
|
|
13680
|
+
};
|
|
13681
|
+
}
|
|
13682
|
+
|
|
13683
|
+
// src/connectors/cloud-run/index.ts
|
|
13684
|
+
var CloudRunConnector = class {
|
|
13685
|
+
constructor(config = {}) {
|
|
13686
|
+
this.config = config;
|
|
13687
|
+
}
|
|
13688
|
+
config;
|
|
13689
|
+
provider = "cloud-run";
|
|
13690
|
+
async poll(ctx) {
|
|
13691
|
+
const creds = readCloudRunCredentials(ctx.credentials);
|
|
13692
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
|
|
13693
|
+
const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
13694
|
+
const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
|
|
13695
|
+
return mapLogEntriesToSignals2(entries);
|
|
13696
|
+
}
|
|
13697
|
+
};
|
|
13698
|
+
function boundedSinceIso(since, now, maxLookbackMs) {
|
|
13699
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13700
|
+
if (!since) return floor.toISOString();
|
|
13701
|
+
const sinceMs = new Date(since).getTime();
|
|
13702
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13703
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13704
|
+
}
|
|
13705
|
+
function createCloudRunConnector(graph, config = {}) {
|
|
13706
|
+
return {
|
|
13707
|
+
connector: new CloudRunConnector(config),
|
|
13708
|
+
resolveTarget: createCloudRunResolveTarget(graph, config)
|
|
13709
|
+
};
|
|
13710
|
+
}
|
|
13711
|
+
|
|
13712
|
+
// src/connectors/render/index.ts
|
|
13713
|
+
init_cjs_shims();
|
|
13714
|
+
var import_types64 = require("@neat.is/types");
|
|
13715
|
+
|
|
13716
|
+
// src/connectors/render/types.ts
|
|
13717
|
+
init_cjs_shims();
|
|
13718
|
+
function readRenderToken(credentials) {
|
|
13719
|
+
const token = credentials.token;
|
|
13720
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
13721
|
+
throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
|
|
13722
|
+
}
|
|
13723
|
+
return token;
|
|
13724
|
+
}
|
|
13725
|
+
function renderLabelValue(entry2, name) {
|
|
13726
|
+
if (!Array.isArray(entry2.labels)) return void 0;
|
|
13727
|
+
const label = entry2.labels.find((l) => l && typeof l === "object" && l.name === name);
|
|
13728
|
+
return label && typeof label.value === "string" ? label.value : void 0;
|
|
13729
|
+
}
|
|
13730
|
+
|
|
13731
|
+
// src/connectors/render/client.ts
|
|
13732
|
+
init_cjs_shims();
|
|
13733
|
+
var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
|
|
13734
|
+
var DEFAULT_RENDER_LOG_LIMIT = 100;
|
|
13735
|
+
var RENDER_MAX_LOG_LIMIT = 100;
|
|
13736
|
+
var DEFAULT_RENDER_MAX_PAGES = 20;
|
|
13737
|
+
var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
|
|
13738
|
+
function clampLimit(limit) {
|
|
13739
|
+
const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
|
|
13740
|
+
if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
|
|
13741
|
+
return Math.min(raw, RENDER_MAX_LOG_LIMIT);
|
|
13742
|
+
}
|
|
13743
|
+
function boundedRenderStartTime(since, now, maxLookbackMs) {
|
|
13744
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13745
|
+
if (!since) return floor.toISOString();
|
|
13746
|
+
const sinceMs = new Date(since).getTime();
|
|
13747
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13748
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13749
|
+
}
|
|
13750
|
+
async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
|
|
13751
|
+
const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
|
|
13752
|
+
url.searchParams.set("ownerId", config.ownerId);
|
|
13753
|
+
url.searchParams.set("resource", config.resourceId);
|
|
13754
|
+
url.searchParams.set("type", "request");
|
|
13755
|
+
url.searchParams.set("startTime", startTime);
|
|
13756
|
+
url.searchParams.set("endTime", endTime);
|
|
13757
|
+
url.searchParams.set("direction", "backward");
|
|
13758
|
+
url.searchParams.set("limit", String(limit));
|
|
13759
|
+
const res = await junctionFetch(
|
|
13760
|
+
url,
|
|
13761
|
+
{ method: "GET", headers: { ...bearerAuthHeader(token) } },
|
|
13762
|
+
{ provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
|
|
13763
|
+
);
|
|
13764
|
+
if (!res.ok) {
|
|
13765
|
+
throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
|
|
13766
|
+
}
|
|
13767
|
+
return await res.json();
|
|
13768
|
+
}
|
|
13769
|
+
async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
|
|
13770
|
+
const limit = clampLimit(config.limit);
|
|
13771
|
+
const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
|
|
13772
|
+
const out = [];
|
|
13773
|
+
let pageStart = startTime;
|
|
13774
|
+
let pageEnd = endTime;
|
|
13775
|
+
for (let page = 0; page < maxPages; page++) {
|
|
13776
|
+
const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
|
|
13777
|
+
if (Array.isArray(body.logs)) out.push(...body.logs);
|
|
13778
|
+
if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
|
|
13779
|
+
pageStart = body.nextStartTime;
|
|
13780
|
+
pageEnd = body.nextEndTime;
|
|
13781
|
+
}
|
|
13782
|
+
return out;
|
|
13783
|
+
}
|
|
13784
|
+
|
|
13785
|
+
// src/connectors/render/index.ts
|
|
13786
|
+
var ROUTE_TARGET_KIND2 = "route";
|
|
13787
|
+
var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
|
|
13788
|
+
function buildRenderRouteIndex(graph, serviceName) {
|
|
13789
|
+
const out = [];
|
|
13790
|
+
graph.forEachNode((_id, attrs) => {
|
|
13791
|
+
const node = attrs;
|
|
13792
|
+
if (node.type !== import_types64.NodeType.RouteNode) return;
|
|
13793
|
+
const route = attrs;
|
|
13794
|
+
if (route.service !== serviceName) return;
|
|
13795
|
+
out.push({
|
|
13796
|
+
method: route.method.toUpperCase(),
|
|
13797
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
13798
|
+
routeNodeId: route.id,
|
|
13799
|
+
path: route.path,
|
|
13800
|
+
line: route.line
|
|
13801
|
+
});
|
|
13802
|
+
});
|
|
13803
|
+
return out;
|
|
13804
|
+
}
|
|
13805
|
+
function findRenderRoute(entries, method, normalizedPath) {
|
|
13806
|
+
return entries.find(
|
|
13807
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
|
|
13808
|
+
);
|
|
13809
|
+
}
|
|
13810
|
+
function bucketKey3(method, normalizedPath) {
|
|
13811
|
+
return `${method} ${normalizedPath}`;
|
|
13812
|
+
}
|
|
13813
|
+
function isHttpErrorStatus2(status2) {
|
|
13814
|
+
return status2 >= 400;
|
|
13815
|
+
}
|
|
13816
|
+
function upsertBucket2(buckets2, key, isError, timestamp, build) {
|
|
13817
|
+
const existing = buckets2.get(key);
|
|
13818
|
+
if (existing) {
|
|
13819
|
+
existing.callCount += 1;
|
|
13820
|
+
if (isError) existing.errorCount += 1;
|
|
13821
|
+
if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
|
|
13822
|
+
return;
|
|
13823
|
+
}
|
|
13824
|
+
buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
|
|
13825
|
+
}
|
|
13826
|
+
function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
13827
|
+
const buckets2 = /* @__PURE__ */ new Map();
|
|
13828
|
+
if (!Array.isArray(entries)) return [];
|
|
13829
|
+
for (const entry2 of entries) {
|
|
13830
|
+
if (!entry2 || typeof entry2 !== "object") continue;
|
|
13831
|
+
if (typeof entry2.timestamp !== "string") continue;
|
|
13832
|
+
const method = renderLabelValue(entry2, "method");
|
|
13833
|
+
const rawPath = renderLabelValue(entry2, "path");
|
|
13834
|
+
if (typeof method !== "string" || method.length === 0) continue;
|
|
13835
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) continue;
|
|
13836
|
+
const methodUpper = method.toUpperCase();
|
|
13837
|
+
const pathOnly = rawPath.split("?")[0];
|
|
13838
|
+
const normalizedPath = normalizePathTemplate(pathOnly);
|
|
13839
|
+
const statusCode = Number.parseInt(renderLabelValue(entry2, "statusCode") ?? "", 10);
|
|
13840
|
+
const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
|
|
13841
|
+
const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
|
|
13842
|
+
if (match) {
|
|
13843
|
+
upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry2.timestamp, () => ({
|
|
13844
|
+
targetKind: ROUTE_TARGET_KIND2,
|
|
13845
|
+
targetName: match.routeNodeId,
|
|
13846
|
+
// RouteNode.line is optional in the schema (packages/types/src/
|
|
13847
|
+
// nodes.ts) even though routes.ts always sets it today — skip the
|
|
13848
|
+
// callSite rather than fabricate a line when it's ever absent
|
|
13849
|
+
// (file-awareness.md §6).
|
|
13850
|
+
...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
|
|
13851
|
+
}));
|
|
13852
|
+
} else {
|
|
13853
|
+
upsertBucket2(
|
|
13854
|
+
buckets2,
|
|
13855
|
+
`unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
|
|
13856
|
+
isError,
|
|
13857
|
+
entry2.timestamp,
|
|
13858
|
+
() => ({
|
|
13859
|
+
targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
|
|
13860
|
+
targetName: bucketKey3(methodUpper, normalizedPath)
|
|
13861
|
+
})
|
|
13862
|
+
);
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
return [...buckets2.values()].map((b) => ({
|
|
13866
|
+
targetKind: b.targetKind,
|
|
13867
|
+
targetName: b.targetName,
|
|
13868
|
+
callCount: b.callCount,
|
|
13869
|
+
errorCount: b.errorCount,
|
|
13870
|
+
lastObservedIso: b.lastObservedIso,
|
|
13871
|
+
...b.callSite ? { callSite: b.callSite } : {}
|
|
13872
|
+
}));
|
|
13873
|
+
}
|
|
13874
|
+
function createRenderResolveTarget(config) {
|
|
13875
|
+
return (signal) => {
|
|
13876
|
+
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
13877
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
13878
|
+
}
|
|
13879
|
+
return null;
|
|
13880
|
+
};
|
|
13881
|
+
}
|
|
13882
|
+
function createRenderConnector(graph, config) {
|
|
13883
|
+
return {
|
|
13884
|
+
provider: "render",
|
|
13885
|
+
async poll(ctx) {
|
|
13886
|
+
const token = readRenderToken(ctx.credentials);
|
|
13887
|
+
const now = /* @__PURE__ */ new Date();
|
|
13888
|
+
const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
|
|
13889
|
+
const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
|
|
13890
|
+
const endTime = now.toISOString();
|
|
13891
|
+
const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
|
|
13892
|
+
const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
|
|
13893
|
+
return mapRenderRequestLogsToSignals(logs, routeIndex);
|
|
13894
|
+
}
|
|
13895
|
+
};
|
|
13896
|
+
}
|
|
13897
|
+
|
|
13204
13898
|
// src/connectors/registry.ts
|
|
13205
13899
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
13206
13900
|
async function authProbe(input) {
|
|
@@ -13376,6 +14070,71 @@ var PROVIDER_DISPATCH = {
|
|
|
13376
14070
|
return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
|
|
13377
14071
|
}
|
|
13378
14072
|
}
|
|
14073
|
+
},
|
|
14074
|
+
"cloud-run": {
|
|
14075
|
+
provider: "cloud-run",
|
|
14076
|
+
// Cloud Run reads both projectId and accessToken from the credential; the
|
|
14077
|
+
// single-string form maps to the secret (the token), and the required-fields
|
|
14078
|
+
// check below catches a projectId that was never supplied.
|
|
14079
|
+
primaryCredentialKey: "accessToken",
|
|
14080
|
+
requiredCredentialFields: ["projectId", "accessToken"],
|
|
14081
|
+
requiredOptionFields: [],
|
|
14082
|
+
build(graph, options) {
|
|
14083
|
+
return createCloudRunConnector(graph, options);
|
|
14084
|
+
},
|
|
14085
|
+
// POST entries:list with pageSize 1 — the exact surface poll() reads, so the
|
|
14086
|
+
// probe checks the actual `logging.logEntries.list` permission the connector
|
|
14087
|
+
// needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
|
|
14088
|
+
// instead check `logging.logs.list`, falsely rejecting a correctly-scoped
|
|
14089
|
+
// custom role that carries only `logging.logEntries.list` (the narrowest
|
|
14090
|
+
// grant docs/connectors/cloud-run.md documents) — the same false-negative
|
|
14091
|
+
// trap Railway's validate avoids by probing its real query. A 2xx means the
|
|
14092
|
+
// token can list log entries; 401/403 means the provider rejected it.
|
|
14093
|
+
validate({ credentials, fetchImpl }) {
|
|
14094
|
+
const projectId = String(credentials.projectId ?? "");
|
|
14095
|
+
return authProbe({
|
|
14096
|
+
provider: "cloud-run",
|
|
14097
|
+
accountKey: projectId || "validate",
|
|
14098
|
+
url: "https://logging.googleapis.com/v2/entries:list",
|
|
14099
|
+
token: String(credentials.accessToken ?? ""),
|
|
14100
|
+
init: {
|
|
14101
|
+
method: "POST",
|
|
14102
|
+
headers: { "Content-Type": "application/json" },
|
|
14103
|
+
body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
|
|
14104
|
+
},
|
|
14105
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14106
|
+
});
|
|
14107
|
+
}
|
|
14108
|
+
},
|
|
14109
|
+
render: {
|
|
14110
|
+
provider: "render",
|
|
14111
|
+
primaryCredentialKey: "token",
|
|
14112
|
+
requiredCredentialFields: ["token"],
|
|
14113
|
+
requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
|
|
14114
|
+
build(graph, options) {
|
|
14115
|
+
const config = options;
|
|
14116
|
+
return {
|
|
14117
|
+
connector: createRenderConnector(graph, config),
|
|
14118
|
+
resolveTarget: createRenderResolveTarget(config)
|
|
14119
|
+
};
|
|
14120
|
+
},
|
|
14121
|
+
// GET /v1/services?limit=1 — the cheapest read the Render API key
|
|
14122
|
+
// authenticates against (render.com/docs/api). Unlike Railway's GraphQL
|
|
14123
|
+
// gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
|
|
14124
|
+
// 401/403, so authProbe's status-code check is a true verdict here. The
|
|
14125
|
+
// logs query itself also needs an ownerId + resource; `services` needs
|
|
14126
|
+
// neither and still fails 401 on a bad token, so it's the honest probe.
|
|
14127
|
+
validate({ credentials, options, fetchImpl }) {
|
|
14128
|
+
const cfg = options;
|
|
14129
|
+
const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
|
|
14130
|
+
return authProbe({
|
|
14131
|
+
provider: "render",
|
|
14132
|
+
accountKey: cfg.ownerId ?? "validate",
|
|
14133
|
+
url: `${baseUrl}/services?limit=1`,
|
|
14134
|
+
token: String(credentials.token ?? ""),
|
|
14135
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14136
|
+
});
|
|
14137
|
+
}
|
|
13379
14138
|
}
|
|
13380
14139
|
};
|
|
13381
14140
|
function vercelCredsFrom(credentials) {
|
|
@@ -13727,11 +14486,11 @@ function registerRoutes(scope, ctx) {
|
|
|
13727
14486
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
13728
14487
|
const parsed = [];
|
|
13729
14488
|
for (const c of candidates) {
|
|
13730
|
-
const r =
|
|
14489
|
+
const r = import_types66.DivergenceTypeSchema.safeParse(c);
|
|
13731
14490
|
if (!r.success) {
|
|
13732
14491
|
return reply.code(400).send({
|
|
13733
14492
|
error: `unknown divergence type "${c}"`,
|
|
13734
|
-
allowed:
|
|
14493
|
+
allowed: import_types66.DivergenceTypeSchema.options
|
|
13735
14494
|
});
|
|
13736
14495
|
}
|
|
13737
14496
|
parsed.push(r.data);
|
|
@@ -14040,7 +14799,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14040
14799
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
14041
14800
|
let violations = await log.readAll();
|
|
14042
14801
|
if (req2.query.severity) {
|
|
14043
|
-
const sev =
|
|
14802
|
+
const sev = import_types66.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
14044
14803
|
if (!sev.success) {
|
|
14045
14804
|
return reply.code(400).send({
|
|
14046
14805
|
error: "invalid severity",
|
|
@@ -14079,7 +14838,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14079
14838
|
scope.post("/policies/check", async (req2, reply) => {
|
|
14080
14839
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
14081
14840
|
if (!proj) return;
|
|
14082
|
-
const parsed =
|
|
14841
|
+
const parsed = import_types66.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
14083
14842
|
if (!parsed.success) {
|
|
14084
14843
|
return reply.code(400).send({
|
|
14085
14844
|
error: "invalid /policies/check body",
|
|
@@ -14420,7 +15179,7 @@ function unroutedErrorsPath(neatHome4) {
|
|
|
14420
15179
|
}
|
|
14421
15180
|
|
|
14422
15181
|
// src/daemon.ts
|
|
14423
|
-
var
|
|
15182
|
+
var import_types67 = require("@neat.is/types");
|
|
14424
15183
|
function daemonJsonPath(scanPath) {
|
|
14425
15184
|
return import_node_path59.default.join(scanPath, "neat-out", "daemon.json");
|
|
14426
15185
|
}
|
|
@@ -14559,7 +15318,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
14559
15318
|
if (!serviceName) return true;
|
|
14560
15319
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
14561
15320
|
return graph.someNode(
|
|
14562
|
-
(_id, attrs) => attrs.type ===
|
|
15321
|
+
(_id, attrs) => attrs.type === import_types67.NodeType.ServiceNode && attrs.name === serviceName
|
|
14563
15322
|
);
|
|
14564
15323
|
}
|
|
14565
15324
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|