@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/index.cjs
CHANGED
|
@@ -8373,6 +8373,55 @@ function columnsFromClassBody(body) {
|
|
|
8373
8373
|
}
|
|
8374
8374
|
return out;
|
|
8375
8375
|
}
|
|
8376
|
+
function foreignKeyParentTable(call) {
|
|
8377
|
+
const fn = call.childForFieldName("function");
|
|
8378
|
+
const t = fn?.text;
|
|
8379
|
+
if (!t) return null;
|
|
8380
|
+
const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
|
|
8381
|
+
if (base !== "ForeignKey") return null;
|
|
8382
|
+
const target = firstPositionalString(call);
|
|
8383
|
+
if (!target) return null;
|
|
8384
|
+
const parts = target.split(".");
|
|
8385
|
+
if (parts.length < 2) return null;
|
|
8386
|
+
return parts[parts.length - 2];
|
|
8387
|
+
}
|
|
8388
|
+
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
8389
|
+
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8390
|
+
const tree = parseSource6(makePyParser4(), file.content);
|
|
8391
|
+
const out = [];
|
|
8392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8393
|
+
walk3(tree.rootNode, (node) => {
|
|
8394
|
+
if (node.type !== "class_definition") return;
|
|
8395
|
+
const body = node.childForFieldName("body");
|
|
8396
|
+
const nameNode = node.childForFieldName("name");
|
|
8397
|
+
if (!body || !nameNode) return;
|
|
8398
|
+
const explicit = explicitTablename(body);
|
|
8399
|
+
if (explicit === "computed") return;
|
|
8400
|
+
let childTable = null;
|
|
8401
|
+
if (explicit) childTable = explicit.name;
|
|
8402
|
+
else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
|
|
8403
|
+
if (!childTable) return;
|
|
8404
|
+
walk3(body, (n) => {
|
|
8405
|
+
if (n.type !== "call") return;
|
|
8406
|
+
const parentTable = foreignKeyParentTable(n);
|
|
8407
|
+
if (!parentTable) return;
|
|
8408
|
+
const key = `${childTable}->${parentTable}`;
|
|
8409
|
+
if (seen.has(key)) return;
|
|
8410
|
+
seen.add(key);
|
|
8411
|
+
const line = n.startPosition.row + 1;
|
|
8412
|
+
out.push({
|
|
8413
|
+
childTable,
|
|
8414
|
+
parentTable,
|
|
8415
|
+
evidence: {
|
|
8416
|
+
file: import_node_path35.default.relative(serviceDir, file.path),
|
|
8417
|
+
line,
|
|
8418
|
+
snippet: snippet(file.content, line)
|
|
8419
|
+
}
|
|
8420
|
+
});
|
|
8421
|
+
});
|
|
8422
|
+
});
|
|
8423
|
+
return out;
|
|
8424
|
+
}
|
|
8376
8425
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
8377
8426
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8378
8427
|
const tree = parseSource6(makePyParser4(), file.content);
|
|
@@ -8718,6 +8767,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
8718
8767
|
walk6(tree.rootNode);
|
|
8719
8768
|
return out;
|
|
8720
8769
|
}
|
|
8770
|
+
function enclosingVarName(call) {
|
|
8771
|
+
let node = call;
|
|
8772
|
+
while (node?.parent) {
|
|
8773
|
+
const parent = node.parent;
|
|
8774
|
+
if (parent.type === "variable_declarator") {
|
|
8775
|
+
const name = parent.childForFieldName("name");
|
|
8776
|
+
return name?.type === "identifier" ? name.text : null;
|
|
8777
|
+
}
|
|
8778
|
+
if (parent.type === "call_expression" || parent.type === "member_expression") {
|
|
8779
|
+
node = parent;
|
|
8780
|
+
continue;
|
|
8781
|
+
}
|
|
8782
|
+
return null;
|
|
8783
|
+
}
|
|
8784
|
+
return null;
|
|
8785
|
+
}
|
|
8786
|
+
function collectDrizzleTables(root) {
|
|
8787
|
+
const tables = [];
|
|
8788
|
+
const varToTable = /* @__PURE__ */ new Map();
|
|
8789
|
+
const walk6 = (node) => {
|
|
8790
|
+
if (node.type === "call_expression") {
|
|
8791
|
+
const fn = node.childForFieldName("function");
|
|
8792
|
+
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
8793
|
+
const args = node.childForFieldName("arguments");
|
|
8794
|
+
const argNodes = args ? namedChildren3(args) : [];
|
|
8795
|
+
const tableName = stringLiteralText2(argNodes[0] ?? null);
|
|
8796
|
+
const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
|
|
8797
|
+
if (tableName) {
|
|
8798
|
+
tables.push({ tableName, object: obj });
|
|
8799
|
+
const varName = enclosingVarName(node);
|
|
8800
|
+
if (varName) varToTable.set(varName, tableName);
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
8804
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8805
|
+
};
|
|
8806
|
+
walk6(root);
|
|
8807
|
+
return { tables, varToTable };
|
|
8808
|
+
}
|
|
8809
|
+
function referencesTargetVar(call) {
|
|
8810
|
+
const fn = call.childForFieldName("function");
|
|
8811
|
+
if (fn?.type !== "member_expression") return null;
|
|
8812
|
+
if (fn.childForFieldName("property")?.text !== "references") return null;
|
|
8813
|
+
const args = call.childForFieldName("arguments");
|
|
8814
|
+
const first = args ? namedChildren3(args)[0] ?? null : null;
|
|
8815
|
+
if (first?.type !== "arrow_function") return null;
|
|
8816
|
+
const body = first.childForFieldName("body");
|
|
8817
|
+
if (body?.type !== "member_expression") return null;
|
|
8818
|
+
const obj = body.childForFieldName("object");
|
|
8819
|
+
return obj?.type === "identifier" ? obj.text : null;
|
|
8820
|
+
}
|
|
8821
|
+
function drizzleForeignKeys(file, serviceDir) {
|
|
8822
|
+
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8823
|
+
const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
|
|
8824
|
+
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
8825
|
+
const out = [];
|
|
8826
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8827
|
+
for (const table of tables) {
|
|
8828
|
+
if (!table.object) continue;
|
|
8829
|
+
const walk6 = (node) => {
|
|
8830
|
+
if (node.type === "call_expression") {
|
|
8831
|
+
const targetVar = referencesTargetVar(node);
|
|
8832
|
+
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
8833
|
+
if (parentTable) {
|
|
8834
|
+
const key = `${table.tableName}->${parentTable}`;
|
|
8835
|
+
if (!seen.has(key)) {
|
|
8836
|
+
seen.add(key);
|
|
8837
|
+
const line = node.startPosition.row + 1;
|
|
8838
|
+
out.push({
|
|
8839
|
+
childTable: table.tableName,
|
|
8840
|
+
parentTable,
|
|
8841
|
+
evidence: {
|
|
8842
|
+
file: import_node_path37.default.relative(serviceDir, file.path),
|
|
8843
|
+
line,
|
|
8844
|
+
snippet: snippet(file.content, line)
|
|
8845
|
+
}
|
|
8846
|
+
});
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8851
|
+
};
|
|
8852
|
+
walk6(table.object);
|
|
8853
|
+
}
|
|
8854
|
+
return out;
|
|
8855
|
+
}
|
|
8721
8856
|
|
|
8722
8857
|
// src/extract/calls/prisma.ts
|
|
8723
8858
|
init_cjs_shims();
|
|
@@ -8846,6 +8981,96 @@ async function prismaColumnEndpoints(serviceDir) {
|
|
|
8846
8981
|
if (!content) return [];
|
|
8847
8982
|
return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
|
|
8848
8983
|
}
|
|
8984
|
+
function buildModelTableMap(lines) {
|
|
8985
|
+
const map = /* @__PURE__ */ new Map();
|
|
8986
|
+
let current = null;
|
|
8987
|
+
let depth = 0;
|
|
8988
|
+
for (const raw of lines) {
|
|
8989
|
+
if (current === null) {
|
|
8990
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
8991
|
+
if (header && raw.includes("{")) {
|
|
8992
|
+
current = { model: header[1], table: header[1] };
|
|
8993
|
+
depth = netBraces(raw);
|
|
8994
|
+
if (depth <= 0) {
|
|
8995
|
+
map.set(current.model, current.table);
|
|
8996
|
+
current = null;
|
|
8997
|
+
}
|
|
8998
|
+
}
|
|
8999
|
+
continue;
|
|
9000
|
+
}
|
|
9001
|
+
depth += netBraces(raw);
|
|
9002
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9003
|
+
if (trimmed.startsWith("@@")) {
|
|
9004
|
+
const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
|
|
9005
|
+
if (m) current.table = m[1];
|
|
9006
|
+
}
|
|
9007
|
+
if (depth <= 0) {
|
|
9008
|
+
map.set(current.model, current.table);
|
|
9009
|
+
current = null;
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
if (current) map.set(current.model, current.table);
|
|
9013
|
+
return map;
|
|
9014
|
+
}
|
|
9015
|
+
function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
9016
|
+
const content = file.content;
|
|
9017
|
+
if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
|
|
9018
|
+
const lines = content.split("\n");
|
|
9019
|
+
const modelToTable = buildModelTableMap(lines);
|
|
9020
|
+
const out = [];
|
|
9021
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9022
|
+
let current = null;
|
|
9023
|
+
let depth = 0;
|
|
9024
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9025
|
+
const raw = lines[i];
|
|
9026
|
+
const lineNo = i + 1;
|
|
9027
|
+
if (current === null) {
|
|
9028
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
9029
|
+
if (header && raw.includes("{")) {
|
|
9030
|
+
current = { table: modelToTable.get(header[1]) ?? header[1] };
|
|
9031
|
+
depth = netBraces(raw);
|
|
9032
|
+
if (depth <= 0) current = null;
|
|
9033
|
+
}
|
|
9034
|
+
continue;
|
|
9035
|
+
}
|
|
9036
|
+
depth += netBraces(raw);
|
|
9037
|
+
const closing = depth <= 0;
|
|
9038
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9039
|
+
if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
|
|
9040
|
+
const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
|
|
9041
|
+
if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
|
|
9042
|
+
const parentTable = modelToTable.get(fm[2]);
|
|
9043
|
+
if (parentTable) {
|
|
9044
|
+
const key = `${current.table}->${parentTable}`;
|
|
9045
|
+
if (!seen.has(key)) {
|
|
9046
|
+
seen.add(key);
|
|
9047
|
+
out.push({
|
|
9048
|
+
childTable: current.table,
|
|
9049
|
+
parentTable,
|
|
9050
|
+
evidence: {
|
|
9051
|
+
file: import_node_path38.default.relative(serviceDir, file.path),
|
|
9052
|
+
line: lineNo,
|
|
9053
|
+
snippet: snippet(content, lineNo)
|
|
9054
|
+
}
|
|
9055
|
+
});
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
9059
|
+
}
|
|
9060
|
+
if (closing) current = null;
|
|
9061
|
+
}
|
|
9062
|
+
return out;
|
|
9063
|
+
}
|
|
9064
|
+
async function prismaForeignKeys(serviceDir) {
|
|
9065
|
+
const schemaPath = await findFirst(serviceDir, [
|
|
9066
|
+
import_node_path38.default.join("prisma", "schema.prisma"),
|
|
9067
|
+
"schema.prisma"
|
|
9068
|
+
]);
|
|
9069
|
+
if (!schemaPath) return [];
|
|
9070
|
+
const content = await readIfExists(schemaPath);
|
|
9071
|
+
if (!content) return [];
|
|
9072
|
+
return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
|
|
9073
|
+
}
|
|
8849
9074
|
|
|
8850
9075
|
// src/extract/calls/go.ts
|
|
8851
9076
|
init_cjs_shims();
|
|
@@ -9022,21 +9247,79 @@ async function addCallEdges(graph, services) {
|
|
|
9022
9247
|
};
|
|
9023
9248
|
}
|
|
9024
9249
|
|
|
9250
|
+
// src/extract/table-edges.ts
|
|
9251
|
+
init_cjs_shims();
|
|
9252
|
+
var import_types31 = require("@neat.is/types");
|
|
9253
|
+
async function addTableEdges(graph, services) {
|
|
9254
|
+
let nodesAdded = 0;
|
|
9255
|
+
let edgesAdded = 0;
|
|
9256
|
+
for (const service of services) {
|
|
9257
|
+
const files = await loadSourceFiles(service.dir);
|
|
9258
|
+
const refs = [];
|
|
9259
|
+
for (const file of files) {
|
|
9260
|
+
try {
|
|
9261
|
+
refs.push(...drizzleForeignKeys(file, service.dir));
|
|
9262
|
+
refs.push(...sqlalchemyForeignKeys(file, service.dir));
|
|
9263
|
+
} catch (err) {
|
|
9264
|
+
recordExtractionError("foreign-key extraction", file.path, err);
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
try {
|
|
9268
|
+
refs.push(...await prismaForeignKeys(service.dir));
|
|
9269
|
+
} catch (err) {
|
|
9270
|
+
recordExtractionError("prisma foreign-key extraction", service.dir, err);
|
|
9271
|
+
}
|
|
9272
|
+
for (const ref of refs) {
|
|
9273
|
+
const childId = (0, import_types31.infraId)("sql-table", ref.childTable);
|
|
9274
|
+
const parentId = (0, import_types31.infraId)("sql-table", ref.parentTable);
|
|
9275
|
+
if (childId === parentId) continue;
|
|
9276
|
+
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
9277
|
+
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
9278
|
+
const edgeId = (0, import_types31.extractedEdgeId)(childId, parentId, import_types31.EdgeType.REFERENCES);
|
|
9279
|
+
if (graph.hasEdge(edgeId)) continue;
|
|
9280
|
+
const edge = {
|
|
9281
|
+
id: edgeId,
|
|
9282
|
+
source: childId,
|
|
9283
|
+
target: parentId,
|
|
9284
|
+
type: import_types31.EdgeType.REFERENCES,
|
|
9285
|
+
provenance: import_types31.Provenance.EXTRACTED,
|
|
9286
|
+
confidence: (0, import_types31.confidenceForExtracted)("structural"),
|
|
9287
|
+
evidence: ref.evidence
|
|
9288
|
+
};
|
|
9289
|
+
graph.addEdgeWithKey(edgeId, childId, parentId, edge);
|
|
9290
|
+
edgesAdded++;
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
return { nodesAdded, edgesAdded };
|
|
9294
|
+
}
|
|
9295
|
+
function ensureTableNode(graph, id, name) {
|
|
9296
|
+
if (graph.hasNode(id)) return 0;
|
|
9297
|
+
const node = {
|
|
9298
|
+
id,
|
|
9299
|
+
type: import_types31.NodeType.InfraNode,
|
|
9300
|
+
name,
|
|
9301
|
+
provider: "self",
|
|
9302
|
+
kind: "sql-table"
|
|
9303
|
+
};
|
|
9304
|
+
graph.addNode(id, node);
|
|
9305
|
+
return 1;
|
|
9306
|
+
}
|
|
9307
|
+
|
|
9025
9308
|
// src/extract/infra/index.ts
|
|
9026
9309
|
init_cjs_shims();
|
|
9027
9310
|
|
|
9028
9311
|
// src/extract/infra/docker-compose.ts
|
|
9029
9312
|
init_cjs_shims();
|
|
9030
9313
|
var import_node_path42 = __toESM(require("path"), 1);
|
|
9031
|
-
var
|
|
9314
|
+
var import_types33 = require("@neat.is/types");
|
|
9032
9315
|
|
|
9033
9316
|
// src/extract/infra/shared.ts
|
|
9034
9317
|
init_cjs_shims();
|
|
9035
|
-
var
|
|
9318
|
+
var import_types32 = require("@neat.is/types");
|
|
9036
9319
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
9037
9320
|
return {
|
|
9038
|
-
id: (0,
|
|
9039
|
-
type:
|
|
9321
|
+
id: (0, import_types32.infraId)(kind, name),
|
|
9322
|
+
type: import_types32.NodeType.InfraNode,
|
|
9040
9323
|
name,
|
|
9041
9324
|
provider,
|
|
9042
9325
|
kind,
|
|
@@ -9080,8 +9363,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
9080
9363
|
source: anchorId,
|
|
9081
9364
|
target: node.id,
|
|
9082
9365
|
type: edgeType,
|
|
9083
|
-
provenance:
|
|
9084
|
-
confidence: (0,
|
|
9366
|
+
provenance: import_types32.Provenance.EXTRACTED,
|
|
9367
|
+
confidence: (0, import_types32.confidenceForExtracted)("structural"),
|
|
9085
9368
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9086
9369
|
};
|
|
9087
9370
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9148,15 +9431,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9148
9431
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
9149
9432
|
const targetId = composeNameToNodeId.get(dep);
|
|
9150
9433
|
if (!targetId) continue;
|
|
9151
|
-
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId,
|
|
9434
|
+
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types33.EdgeType.DEPENDS_ON);
|
|
9152
9435
|
if (graph.hasEdge(edgeId)) continue;
|
|
9153
9436
|
const edge = {
|
|
9154
9437
|
id: edgeId,
|
|
9155
9438
|
source: sourceId,
|
|
9156
9439
|
target: targetId,
|
|
9157
|
-
type:
|
|
9158
|
-
provenance:
|
|
9159
|
-
confidence: (0,
|
|
9440
|
+
type: import_types33.EdgeType.DEPENDS_ON,
|
|
9441
|
+
provenance: import_types33.Provenance.EXTRACTED,
|
|
9442
|
+
confidence: (0, import_types33.confidenceForExtracted)("structural"),
|
|
9160
9443
|
evidence: { file: evidenceFile }
|
|
9161
9444
|
};
|
|
9162
9445
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9170,7 +9453,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9170
9453
|
init_cjs_shims();
|
|
9171
9454
|
var import_node_path43 = __toESM(require("path"), 1);
|
|
9172
9455
|
var import_node_fs17 = require("fs");
|
|
9173
|
-
var
|
|
9456
|
+
var import_types34 = require("@neat.is/types");
|
|
9174
9457
|
function readDockerfile(content) {
|
|
9175
9458
|
let image = null;
|
|
9176
9459
|
const ports = [];
|
|
@@ -9229,15 +9512,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9229
9512
|
);
|
|
9230
9513
|
nodesAdded += fn;
|
|
9231
9514
|
edgesAdded += fe;
|
|
9232
|
-
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id,
|
|
9515
|
+
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types34.EdgeType.RUNS_ON);
|
|
9233
9516
|
if (!graph.hasEdge(edgeId)) {
|
|
9234
9517
|
const edge = {
|
|
9235
9518
|
id: edgeId,
|
|
9236
9519
|
source: fileNodeId,
|
|
9237
9520
|
target: node.id,
|
|
9238
|
-
type:
|
|
9239
|
-
provenance:
|
|
9240
|
-
confidence: (0,
|
|
9521
|
+
type: import_types34.EdgeType.RUNS_ON,
|
|
9522
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9523
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9241
9524
|
evidence: {
|
|
9242
9525
|
file: evidenceFile,
|
|
9243
9526
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -9252,15 +9535,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9252
9535
|
graph.addNode(portNode.id, portNode);
|
|
9253
9536
|
nodesAdded++;
|
|
9254
9537
|
}
|
|
9255
|
-
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id,
|
|
9538
|
+
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types34.EdgeType.CONNECTS_TO);
|
|
9256
9539
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
9257
9540
|
const portEdge = {
|
|
9258
9541
|
id: portEdgeId,
|
|
9259
9542
|
source: fileNodeId,
|
|
9260
9543
|
target: portNode.id,
|
|
9261
|
-
type:
|
|
9262
|
-
provenance:
|
|
9263
|
-
confidence: (0,
|
|
9544
|
+
type: import_types34.EdgeType.CONNECTS_TO,
|
|
9545
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9546
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9264
9547
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
9265
9548
|
};
|
|
9266
9549
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -9274,7 +9557,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9274
9557
|
init_cjs_shims();
|
|
9275
9558
|
var import_node_fs18 = require("fs");
|
|
9276
9559
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
9277
|
-
var
|
|
9560
|
+
var import_types35 = require("@neat.is/types");
|
|
9278
9561
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
9279
9562
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
9280
9563
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -9355,16 +9638,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
9355
9638
|
if (!target) continue;
|
|
9356
9639
|
if (seen.has(target.nodeId)) continue;
|
|
9357
9640
|
seen.add(target.nodeId);
|
|
9358
|
-
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
9641
|
+
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types35.EdgeType.DEPENDS_ON);
|
|
9359
9642
|
if (graph.hasEdge(edgeId)) continue;
|
|
9360
9643
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
9361
9644
|
const edge = {
|
|
9362
9645
|
id: edgeId,
|
|
9363
9646
|
source: resource.nodeId,
|
|
9364
9647
|
target: target.nodeId,
|
|
9365
|
-
type:
|
|
9366
|
-
provenance:
|
|
9367
|
-
confidence: (0,
|
|
9648
|
+
type: import_types35.EdgeType.DEPENDS_ON,
|
|
9649
|
+
provenance: import_types35.Provenance.EXTRACTED,
|
|
9650
|
+
confidence: (0, import_types35.confidenceForExtracted)("structural"),
|
|
9368
9651
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
9369
9652
|
};
|
|
9370
9653
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9436,7 +9719,7 @@ init_cjs_shims();
|
|
|
9436
9719
|
var import_node_fs20 = require("fs");
|
|
9437
9720
|
var import_node_path46 = __toESM(require("path"), 1);
|
|
9438
9721
|
var import_smol_toml2 = require("smol-toml");
|
|
9439
|
-
var
|
|
9722
|
+
var import_types36 = require("@neat.is/types");
|
|
9440
9723
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
9441
9724
|
async function readWranglerConfig(dir) {
|
|
9442
9725
|
for (const filename of WRANGLER_FILENAMES) {
|
|
@@ -9484,8 +9767,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
9484
9767
|
source: anchorId,
|
|
9485
9768
|
target: node.id,
|
|
9486
9769
|
type: edgeType,
|
|
9487
|
-
provenance:
|
|
9488
|
-
confidence: (0,
|
|
9770
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9771
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9489
9772
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9490
9773
|
};
|
|
9491
9774
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9546,15 +9829,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9546
9829
|
nodesAdded++;
|
|
9547
9830
|
}
|
|
9548
9831
|
if (runtimeNode.id !== anchorId) {
|
|
9549
|
-
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
9832
|
+
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types36.EdgeType.RUNS_ON);
|
|
9550
9833
|
if (!graph.hasEdge(runsOnId)) {
|
|
9551
9834
|
const edge = {
|
|
9552
9835
|
id: runsOnId,
|
|
9553
9836
|
source: anchorId,
|
|
9554
9837
|
target: runtimeNode.id,
|
|
9555
|
-
type:
|
|
9556
|
-
provenance:
|
|
9557
|
-
confidence: (0,
|
|
9838
|
+
type: import_types36.EdgeType.RUNS_ON,
|
|
9839
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9840
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9558
9841
|
evidence: {
|
|
9559
9842
|
file: evidenceFile,
|
|
9560
9843
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -9568,7 +9851,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9568
9851
|
const result = addResourceEdge(
|
|
9569
9852
|
graph,
|
|
9570
9853
|
anchorId,
|
|
9571
|
-
|
|
9854
|
+
import_types36.EdgeType.CONNECTS_TO,
|
|
9572
9855
|
"cloudflare-route",
|
|
9573
9856
|
route,
|
|
9574
9857
|
evidenceFile,
|
|
@@ -9592,7 +9875,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9592
9875
|
const result = addResourceEdge(
|
|
9593
9876
|
graph,
|
|
9594
9877
|
anchorId,
|
|
9595
|
-
|
|
9878
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9596
9879
|
group.kind,
|
|
9597
9880
|
name,
|
|
9598
9881
|
evidenceFile,
|
|
@@ -9606,7 +9889,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9606
9889
|
const result = addResourceEdge(
|
|
9607
9890
|
graph,
|
|
9608
9891
|
anchorId,
|
|
9609
|
-
|
|
9892
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9610
9893
|
"cloudflare-cron",
|
|
9611
9894
|
cron,
|
|
9612
9895
|
evidenceFile,
|
|
@@ -9619,7 +9902,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9619
9902
|
const result = addResourceEdge(
|
|
9620
9903
|
graph,
|
|
9621
9904
|
anchorId,
|
|
9622
|
-
|
|
9905
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9623
9906
|
"cloudflare-env-var",
|
|
9624
9907
|
varName,
|
|
9625
9908
|
evidenceFile,
|
|
@@ -9632,15 +9915,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9632
9915
|
if (!svc.service) continue;
|
|
9633
9916
|
const target = workerIndex.get(svc.service);
|
|
9634
9917
|
if (target && target.anchorId !== anchorId) {
|
|
9635
|
-
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId,
|
|
9918
|
+
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types36.EdgeType.CALLS);
|
|
9636
9919
|
if (!graph.hasEdge(edgeId)) {
|
|
9637
9920
|
const edge = {
|
|
9638
9921
|
id: edgeId,
|
|
9639
9922
|
source: anchorId,
|
|
9640
9923
|
target: target.anchorId,
|
|
9641
|
-
type:
|
|
9642
|
-
provenance:
|
|
9643
|
-
confidence: (0,
|
|
9924
|
+
type: import_types36.EdgeType.CALLS,
|
|
9925
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9926
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9644
9927
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
9645
9928
|
};
|
|
9646
9929
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9651,7 +9934,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9651
9934
|
const result = addResourceEdge(
|
|
9652
9935
|
graph,
|
|
9653
9936
|
anchorId,
|
|
9654
|
-
|
|
9937
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9655
9938
|
"cloudflare-service-binding",
|
|
9656
9939
|
svc.service,
|
|
9657
9940
|
evidenceFile,
|
|
@@ -9668,7 +9951,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9668
9951
|
init_cjs_shims();
|
|
9669
9952
|
var import_node_fs21 = require("fs");
|
|
9670
9953
|
var import_node_path47 = __toESM(require("path"), 1);
|
|
9671
|
-
var
|
|
9954
|
+
var import_types37 = require("@neat.is/types");
|
|
9672
9955
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
9673
9956
|
async function readVercelConfig(dir) {
|
|
9674
9957
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
@@ -9731,12 +10014,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
9731
10014
|
nodesAdded += result.nodesAdded;
|
|
9732
10015
|
edgesAdded += result.edgesAdded;
|
|
9733
10016
|
};
|
|
9734
|
-
add(
|
|
9735
|
-
for (const cron of config.crons ?? []) add(
|
|
9736
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
9737
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
10017
|
+
add(import_types37.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
10018
|
+
for (const cron of config.crons ?? []) add(import_types37.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
10019
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
10020
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
9738
10021
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
9739
|
-
add(
|
|
10022
|
+
add(import_types37.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
9740
10023
|
}
|
|
9741
10024
|
}
|
|
9742
10025
|
return { nodesAdded, edgesAdded };
|
|
@@ -9747,7 +10030,7 @@ init_cjs_shims();
|
|
|
9747
10030
|
var import_node_fs22 = require("fs");
|
|
9748
10031
|
var import_node_path48 = __toESM(require("path"), 1);
|
|
9749
10032
|
var import_smol_toml3 = require("smol-toml");
|
|
9750
|
-
var
|
|
10033
|
+
var import_types38 = require("@neat.is/types");
|
|
9751
10034
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
9752
10035
|
async function readRailwayConfig(dir) {
|
|
9753
10036
|
for (const filename of RAILWAY_FILENAMES) {
|
|
@@ -9793,9 +10076,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
9793
10076
|
nodesAdded += result.nodesAdded;
|
|
9794
10077
|
edgesAdded += result.edgesAdded;
|
|
9795
10078
|
};
|
|
9796
|
-
add(
|
|
9797
|
-
add(
|
|
9798
|
-
add(
|
|
10079
|
+
add(import_types38.EdgeType.RUNS_ON, "railway", "railway");
|
|
10080
|
+
add(import_types38.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
10081
|
+
add(import_types38.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
9799
10082
|
}
|
|
9800
10083
|
return { nodesAdded, edgesAdded };
|
|
9801
10084
|
}
|
|
@@ -9805,7 +10088,7 @@ init_cjs_shims();
|
|
|
9805
10088
|
var import_node_fs23 = require("fs");
|
|
9806
10089
|
var import_node_path49 = __toESM(require("path"), 1);
|
|
9807
10090
|
var import_smol_toml4 = require("smol-toml");
|
|
9808
|
-
var
|
|
10091
|
+
var import_types39 = require("@neat.is/types");
|
|
9809
10092
|
async function readSupabaseConfig(dir) {
|
|
9810
10093
|
const relFile = import_node_path49.default.join("supabase", "config.toml");
|
|
9811
10094
|
const abs = import_node_path49.default.join(dir, relFile);
|
|
@@ -9853,10 +10136,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
9853
10136
|
nodesAdded += result.nodesAdded;
|
|
9854
10137
|
edgesAdded += result.edgesAdded;
|
|
9855
10138
|
};
|
|
9856
|
-
add(
|
|
9857
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
9858
|
-
if (config.storage) add(
|
|
9859
|
-
if (config.auth) add(
|
|
10139
|
+
add(import_types39.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
10140
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types39.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
10141
|
+
if (config.storage) add(import_types39.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
10142
|
+
if (config.auth) add(import_types39.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
9860
10143
|
}
|
|
9861
10144
|
return { nodesAdded, edgesAdded };
|
|
9862
10145
|
}
|
|
@@ -9884,11 +10167,11 @@ var import_node_path51 = __toESM(require("path"), 1);
|
|
|
9884
10167
|
init_cjs_shims();
|
|
9885
10168
|
var import_node_fs24 = require("fs");
|
|
9886
10169
|
var import_node_path50 = __toESM(require("path"), 1);
|
|
9887
|
-
var
|
|
10170
|
+
var import_types40 = require("@neat.is/types");
|
|
9888
10171
|
function dropOrphanedFileNodes(graph) {
|
|
9889
10172
|
const orphans = [];
|
|
9890
10173
|
graph.forEachNode((id, attrs) => {
|
|
9891
|
-
if (attrs.type !==
|
|
10174
|
+
if (attrs.type !== import_types40.NodeType.FileNode) return;
|
|
9892
10175
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
9893
10176
|
orphans.push(id);
|
|
9894
10177
|
}
|
|
@@ -9901,7 +10184,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
9901
10184
|
const bases = [scanPath, ...serviceDirs];
|
|
9902
10185
|
graph.forEachEdge((id, attrs) => {
|
|
9903
10186
|
const edge = attrs;
|
|
9904
|
-
if (edge.provenance !==
|
|
10187
|
+
if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
|
|
9905
10188
|
const evidenceFile = edge.evidence?.file;
|
|
9906
10189
|
if (!evidenceFile) return;
|
|
9907
10190
|
if (import_node_path50.default.isAbsolute(evidenceFile)) {
|
|
@@ -9932,6 +10215,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9932
10215
|
const routePhase = await addRoutes(graph, services);
|
|
9933
10216
|
const grpcPhase = await addGrpcMethods(graph, services);
|
|
9934
10217
|
const phase4 = await addCallEdges(graph, services);
|
|
10218
|
+
const tableEdges = await addTableEdges(graph, services);
|
|
9935
10219
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
9936
10220
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
9937
10221
|
graph,
|
|
@@ -9971,8 +10255,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9971
10255
|
}
|
|
9972
10256
|
}
|
|
9973
10257
|
const result = {
|
|
9974
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
9975
|
-
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
10258
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
|
|
10259
|
+
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
|
|
9976
10260
|
frontiersPromoted,
|
|
9977
10261
|
extractionErrors: errorEntries.length,
|
|
9978
10262
|
errorEntries,
|
|
@@ -9997,7 +10281,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9997
10281
|
init_cjs_shims();
|
|
9998
10282
|
var import_node_fs25 = require("fs");
|
|
9999
10283
|
var import_node_path52 = __toESM(require("path"), 1);
|
|
10000
|
-
var
|
|
10284
|
+
var import_types41 = require("@neat.is/types");
|
|
10001
10285
|
var SCHEMA_VERSION = 6;
|
|
10002
10286
|
function migrateV1ToV2(payload) {
|
|
10003
10287
|
const nodes = payload.graph.nodes;
|
|
@@ -10021,7 +10305,7 @@ function migrateV5ToV6(payload) {
|
|
|
10021
10305
|
if (Array.isArray(nodes)) {
|
|
10022
10306
|
for (const node of nodes) {
|
|
10023
10307
|
const attrs = node.attributes;
|
|
10024
|
-
if (!attrs || attrs.type !==
|
|
10308
|
+
if (!attrs || attrs.type !== import_types41.NodeType.InfraNode) continue;
|
|
10025
10309
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
10026
10310
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
10027
10311
|
}
|
|
@@ -10034,12 +10318,12 @@ function migrateV2ToV3(payload) {
|
|
|
10034
10318
|
for (const edge of edges) {
|
|
10035
10319
|
const attrs = edge.attributes;
|
|
10036
10320
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
10037
|
-
attrs.provenance =
|
|
10321
|
+
attrs.provenance = import_types41.Provenance.OBSERVED;
|
|
10038
10322
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
10039
10323
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
10040
10324
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
10041
10325
|
if (type && source && target) {
|
|
10042
|
-
const newId = (0,
|
|
10326
|
+
const newId = (0, import_types41.observedEdgeId)(source, target, type);
|
|
10043
10327
|
attrs.id = newId;
|
|
10044
10328
|
if (edge.key) edge.key = newId;
|
|
10045
10329
|
}
|
|
@@ -10137,7 +10421,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
10137
10421
|
init_cjs_shims();
|
|
10138
10422
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
10139
10423
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
10140
|
-
var
|
|
10424
|
+
var import_types66 = require("@neat.is/types");
|
|
10141
10425
|
|
|
10142
10426
|
// src/extend/index.ts
|
|
10143
10427
|
init_cjs_shims();
|
|
@@ -10484,39 +10768,39 @@ async function rollbackExtension(ctx, args) {
|
|
|
10484
10768
|
|
|
10485
10769
|
// src/divergences.ts
|
|
10486
10770
|
init_cjs_shims();
|
|
10487
|
-
var
|
|
10771
|
+
var import_types42 = require("@neat.is/types");
|
|
10488
10772
|
function bucketKey(source, target, type) {
|
|
10489
10773
|
return `${type}|${source}|${target}`;
|
|
10490
10774
|
}
|
|
10491
10775
|
function bucketSourceFor(graph, edge) {
|
|
10492
|
-
if (edge.type !==
|
|
10493
|
-
const parsed = (0,
|
|
10776
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) return edge.source;
|
|
10777
|
+
const parsed = (0, import_types42.parseFileId)(edge.source);
|
|
10494
10778
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
10495
10779
|
const target = graph.getNodeAttributes(edge.target);
|
|
10496
|
-
if (target.type !==
|
|
10497
|
-
return (0,
|
|
10780
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) return edge.source;
|
|
10781
|
+
return (0, import_types42.serviceId)(parsed.service);
|
|
10498
10782
|
}
|
|
10499
10783
|
function bucketEdges(graph) {
|
|
10500
10784
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10501
10785
|
graph.forEachEdge((id, attrs) => {
|
|
10502
10786
|
const e = attrs;
|
|
10503
|
-
const parsed = (0,
|
|
10787
|
+
const parsed = (0, import_types42.parseEdgeId)(id);
|
|
10504
10788
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
10505
10789
|
const source = bucketSourceFor(graph, e);
|
|
10506
10790
|
const key = bucketKey(source, e.target, e.type);
|
|
10507
10791
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
10508
10792
|
switch (provenance) {
|
|
10509
|
-
case
|
|
10793
|
+
case import_types42.Provenance.EXTRACTED:
|
|
10510
10794
|
cur.extracted = e;
|
|
10511
10795
|
break;
|
|
10512
|
-
case
|
|
10796
|
+
case import_types42.Provenance.OBSERVED:
|
|
10513
10797
|
cur.observed = e;
|
|
10514
10798
|
break;
|
|
10515
|
-
case
|
|
10799
|
+
case import_types42.Provenance.INFERRED:
|
|
10516
10800
|
cur.inferred = e;
|
|
10517
10801
|
break;
|
|
10518
10802
|
default:
|
|
10519
|
-
if (e.provenance ===
|
|
10803
|
+
if (e.provenance === import_types42.Provenance.STALE) cur.stale = e;
|
|
10520
10804
|
}
|
|
10521
10805
|
buckets2.set(key, cur);
|
|
10522
10806
|
});
|
|
@@ -10525,17 +10809,17 @@ function bucketEdges(graph) {
|
|
|
10525
10809
|
function nodeIsFrontier(graph, nodeId) {
|
|
10526
10810
|
if (!graph.hasNode(nodeId)) return false;
|
|
10527
10811
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10528
|
-
return attrs.type ===
|
|
10812
|
+
return attrs.type === import_types42.NodeType.FrontierNode;
|
|
10529
10813
|
}
|
|
10530
10814
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
10531
10815
|
if (!graph.hasNode(nodeId)) return false;
|
|
10532
10816
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10533
|
-
return attrs.type ===
|
|
10817
|
+
return attrs.type === import_types42.NodeType.WebSocketChannelNode;
|
|
10534
10818
|
}
|
|
10535
10819
|
function nodeIsSymbol(graph, nodeId) {
|
|
10536
10820
|
if (!graph.hasNode(nodeId)) return false;
|
|
10537
10821
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10538
|
-
return attrs.type ===
|
|
10822
|
+
return attrs.type === import_types42.NodeType.SymbolNode;
|
|
10539
10823
|
}
|
|
10540
10824
|
function clampConfidence(n) {
|
|
10541
10825
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -10555,14 +10839,14 @@ function gradedConfidence(edge) {
|
|
|
10555
10839
|
return clampConfidence(confidenceForEdge(edge));
|
|
10556
10840
|
}
|
|
10557
10841
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10842
|
+
import_types42.EdgeType.CALLS,
|
|
10843
|
+
import_types42.EdgeType.CONNECTS_TO,
|
|
10844
|
+
import_types42.EdgeType.PUBLISHES_TO,
|
|
10845
|
+
import_types42.EdgeType.CONSUMES_FROM
|
|
10562
10846
|
]);
|
|
10563
10847
|
function detectMissingDivergences(graph, bucket) {
|
|
10564
10848
|
const out = [];
|
|
10565
|
-
if (bucket.type ===
|
|
10849
|
+
if (bucket.type === import_types42.EdgeType.CONTAINS) return out;
|
|
10566
10850
|
if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
|
|
10567
10851
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
10568
10852
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
@@ -10604,7 +10888,7 @@ function declaredHostFor(svc) {
|
|
|
10604
10888
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
10605
10889
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10606
10890
|
const e = graph.getEdgeAttributes(edgeId);
|
|
10607
|
-
if (e.type ===
|
|
10891
|
+
if (e.type === import_types42.EdgeType.CONFIGURED_BY && e.provenance === import_types42.Provenance.EXTRACTED) {
|
|
10608
10892
|
return true;
|
|
10609
10893
|
}
|
|
10610
10894
|
}
|
|
@@ -10617,10 +10901,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
10617
10901
|
const out = [];
|
|
10618
10902
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10619
10903
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10620
|
-
if (edge.type !==
|
|
10621
|
-
if (edge.provenance !==
|
|
10904
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) continue;
|
|
10905
|
+
if (edge.provenance !== import_types42.Provenance.OBSERVED) continue;
|
|
10622
10906
|
const target = graph.getNodeAttributes(edge.target);
|
|
10623
|
-
if (target.type !==
|
|
10907
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) continue;
|
|
10624
10908
|
const observedHost = target.host?.trim();
|
|
10625
10909
|
if (!observedHost) continue;
|
|
10626
10910
|
if (observedHost === declaredHost) continue;
|
|
@@ -10642,10 +10926,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
10642
10926
|
const deps = svc.dependencies ?? {};
|
|
10643
10927
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10644
10928
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10645
|
-
if (edge.type !==
|
|
10646
|
-
if (edge.provenance !==
|
|
10929
|
+
if (edge.type !== import_types42.EdgeType.CONNECTS_TO) continue;
|
|
10930
|
+
if (edge.provenance !== import_types42.Provenance.OBSERVED) continue;
|
|
10647
10931
|
const target = graph.getNodeAttributes(edge.target);
|
|
10648
|
-
if (target.type !==
|
|
10932
|
+
if (target.type !== import_types42.NodeType.DatabaseNode) continue;
|
|
10649
10933
|
for (const pair of compatPairs()) {
|
|
10650
10934
|
if (pair.engine !== target.engine) continue;
|
|
10651
10935
|
const declared = deps[pair.driver];
|
|
@@ -10742,7 +11026,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
10742
11026
|
for (const d of all) {
|
|
10743
11027
|
if (d.type !== "host-mismatch") continue;
|
|
10744
11028
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
10745
|
-
declaredHalf.add((0,
|
|
11029
|
+
declaredHalf.add((0, import_types42.databaseId)(d.extractedHost));
|
|
10746
11030
|
}
|
|
10747
11031
|
if (observedHalf.size === 0) return all;
|
|
10748
11032
|
return all.filter((d) => {
|
|
@@ -10761,13 +11045,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10761
11045
|
}
|
|
10762
11046
|
graph.forEachNode((nodeId, attrs) => {
|
|
10763
11047
|
const n = attrs;
|
|
10764
|
-
if (n.type ===
|
|
11048
|
+
if (n.type === import_types42.NodeType.ServiceNode) {
|
|
10765
11049
|
const svc = n;
|
|
10766
11050
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
10767
11051
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
10768
11052
|
return;
|
|
10769
11053
|
}
|
|
10770
|
-
if (n.type ===
|
|
11054
|
+
if (n.type === import_types42.NodeType.InfraNode && n.kind === "sql-table") {
|
|
10771
11055
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
10772
11056
|
}
|
|
10773
11057
|
});
|
|
@@ -10803,7 +11087,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10803
11087
|
const bc = "column" in b && b.column ? b.column : "";
|
|
10804
11088
|
return ac.localeCompare(bc);
|
|
10805
11089
|
});
|
|
10806
|
-
return
|
|
11090
|
+
return import_types42.DivergenceResultSchema.parse({
|
|
10807
11091
|
divergences: filtered,
|
|
10808
11092
|
totalAffected: filtered.length,
|
|
10809
11093
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -10989,7 +11273,7 @@ init_cjs_shims();
|
|
|
10989
11273
|
var import_node_fs29 = require("fs");
|
|
10990
11274
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
10991
11275
|
var import_node_path56 = __toESM(require("path"), 1);
|
|
10992
|
-
var
|
|
11276
|
+
var import_types43 = require("@neat.is/types");
|
|
10993
11277
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
10994
11278
|
var LOCK_RETRY_MS = 50;
|
|
10995
11279
|
function neatHome() {
|
|
@@ -11199,10 +11483,10 @@ async function readRegistry() {
|
|
|
11199
11483
|
throw err;
|
|
11200
11484
|
}
|
|
11201
11485
|
const parsed = JSON.parse(raw);
|
|
11202
|
-
return
|
|
11486
|
+
return import_types43.RegistryFileSchema.parse(parsed);
|
|
11203
11487
|
}
|
|
11204
11488
|
async function writeRegistry(reg) {
|
|
11205
|
-
const validated =
|
|
11489
|
+
const validated = import_types43.RegistryFileSchema.parse(reg);
|
|
11206
11490
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
11207
11491
|
}
|
|
11208
11492
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -11603,15 +11887,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
11603
11887
|
|
|
11604
11888
|
// src/connectors/index.ts
|
|
11605
11889
|
init_cjs_shims();
|
|
11606
|
-
var
|
|
11890
|
+
var import_types44 = require("@neat.is/types");
|
|
11607
11891
|
var NO_ENV = "unknown";
|
|
11608
11892
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
11609
11893
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11610
11894
|
const sites = [];
|
|
11611
11895
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
11612
11896
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
11613
|
-
if (edge.provenance !==
|
|
11614
|
-
const parsed = (0,
|
|
11897
|
+
if (edge.provenance !== import_types44.Provenance.EXTRACTED) continue;
|
|
11898
|
+
const parsed = (0, import_types44.parseFileId)(edge.source);
|
|
11615
11899
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
11616
11900
|
const site = { relPath: edge.evidence.file };
|
|
11617
11901
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -11622,7 +11906,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
11622
11906
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
11623
11907
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11624
11908
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
11625
|
-
if (attrs.type !==
|
|
11909
|
+
if (attrs.type !== import_types44.NodeType.RouteNode || !attrs.path) return void 0;
|
|
11626
11910
|
const site = { relPath: attrs.path };
|
|
11627
11911
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
11628
11912
|
return site;
|
|
@@ -11806,7 +12090,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
|
|
|
11806
12090
|
// connector add/remove/test` (provision/deprovision/validate), never a poll
|
|
11807
12091
|
// loop, so this bucket is exercised a handful of times per command. Kept
|
|
11808
12092
|
// conservative pending a documented Drains-API rate limit.
|
|
11809
|
-
vercel: { capacity: 20, refillMs: 5e3 }
|
|
12093
|
+
vercel: { capacity: 20, refillMs: 5e3 },
|
|
12094
|
+
// Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
|
|
12095
|
+
// pinned here to a single confirmed number — this is a conservative
|
|
12096
|
+
// placeholder pending a live project, matching the other pull providers.
|
|
12097
|
+
render: { capacity: 30, refillMs: 1e4 }
|
|
11810
12098
|
};
|
|
11811
12099
|
var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
|
|
11812
12100
|
function defaultRateLimitFor(provider) {
|
|
@@ -12213,23 +12501,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
12213
12501
|
|
|
12214
12502
|
// src/connectors/supabase/resolve.ts
|
|
12215
12503
|
init_cjs_shims();
|
|
12216
|
-
var
|
|
12504
|
+
var import_types46 = require("@neat.is/types");
|
|
12217
12505
|
function createSupabaseResolveTarget(graph, config) {
|
|
12218
12506
|
return (signal, _ctx) => {
|
|
12219
12507
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
12220
12508
|
return null;
|
|
12221
12509
|
}
|
|
12222
|
-
const subResourceId = (0,
|
|
12510
|
+
const subResourceId = (0, import_types46.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
12223
12511
|
if (graph.hasNode(subResourceId)) {
|
|
12224
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
12512
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12225
12513
|
}
|
|
12226
|
-
const bareResourceId = (0,
|
|
12514
|
+
const bareResourceId = (0, import_types46.infraId)(signal.targetKind, signal.targetName);
|
|
12227
12515
|
if (graph.hasNode(bareResourceId)) {
|
|
12228
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
12516
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12229
12517
|
}
|
|
12230
|
-
const projectLevelId = (0,
|
|
12518
|
+
const projectLevelId = (0, import_types46.infraId)("supabase", config.nodeRef);
|
|
12231
12519
|
if (graph.hasNode(projectLevelId)) {
|
|
12232
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
12520
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
|
|
12233
12521
|
}
|
|
12234
12522
|
return null;
|
|
12235
12523
|
};
|
|
@@ -12322,7 +12610,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
12322
12610
|
|
|
12323
12611
|
// src/connectors/railway/index.ts
|
|
12324
12612
|
init_cjs_shims();
|
|
12325
|
-
var
|
|
12613
|
+
var import_types50 = require("@neat.is/types");
|
|
12326
12614
|
|
|
12327
12615
|
// src/connectors/railway/client.ts
|
|
12328
12616
|
init_cjs_shims();
|
|
@@ -12473,7 +12761,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
12473
12761
|
const out = [];
|
|
12474
12762
|
graph.forEachNode((_id, attrs) => {
|
|
12475
12763
|
const node = attrs;
|
|
12476
|
-
if (node.type !==
|
|
12764
|
+
if (node.type !== import_types50.NodeType.RouteNode) return;
|
|
12477
12765
|
const route = attrs;
|
|
12478
12766
|
if (route.service !== serviceName) return;
|
|
12479
12767
|
out.push({
|
|
@@ -12577,12 +12865,12 @@ function createRailwayResolveTarget(config) {
|
|
|
12577
12865
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
12578
12866
|
if (!serviceName) return null;
|
|
12579
12867
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
12580
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
12868
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types50.EdgeType.CALLS };
|
|
12581
12869
|
}
|
|
12582
12870
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
12583
12871
|
const peerName = config.serviceNameById[signal.targetName];
|
|
12584
12872
|
if (!peerName) return null;
|
|
12585
|
-
return { targetNodeId: (0,
|
|
12873
|
+
return { targetNodeId: (0, import_types50.serviceId)(peerName), serviceName, edgeType: import_types50.EdgeType.CONNECTS_TO };
|
|
12586
12874
|
}
|
|
12587
12875
|
return null;
|
|
12588
12876
|
};
|
|
@@ -12770,7 +13058,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
12770
13058
|
|
|
12771
13059
|
// src/connectors/firebase/resolve.ts
|
|
12772
13060
|
init_cjs_shims();
|
|
12773
|
-
var
|
|
13061
|
+
var import_types51 = require("@neat.is/types");
|
|
12774
13062
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
12775
13063
|
switch (resourceType) {
|
|
12776
13064
|
case "cloud_function":
|
|
@@ -12785,7 +13073,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
12785
13073
|
const entries = [];
|
|
12786
13074
|
graph.forEachNode((_id, attrs) => {
|
|
12787
13075
|
const node = attrs;
|
|
12788
|
-
if (node.type !==
|
|
13076
|
+
if (node.type !== import_types51.NodeType.RouteNode) return;
|
|
12789
13077
|
const route = attrs;
|
|
12790
13078
|
if (route.service !== serviceName) return;
|
|
12791
13079
|
entries.push({
|
|
@@ -12817,7 +13105,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
12817
13105
|
return {
|
|
12818
13106
|
targetNodeId: match.routeNodeId,
|
|
12819
13107
|
serviceName,
|
|
12820
|
-
edgeType:
|
|
13108
|
+
edgeType: import_types51.EdgeType.CALLS
|
|
12821
13109
|
};
|
|
12822
13110
|
};
|
|
12823
13111
|
}
|
|
@@ -12844,7 +13132,7 @@ init_cjs_shims();
|
|
|
12844
13132
|
|
|
12845
13133
|
// src/connectors/cloudflare/connector.ts
|
|
12846
13134
|
init_cjs_shims();
|
|
12847
|
-
var
|
|
13135
|
+
var import_types53 = require("@neat.is/types");
|
|
12848
13136
|
|
|
12849
13137
|
// src/connectors/cloudflare/client.ts
|
|
12850
13138
|
init_cjs_shims();
|
|
@@ -13008,7 +13296,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
13008
13296
|
graph.forEachNode((id, attrs) => {
|
|
13009
13297
|
if (found) return;
|
|
13010
13298
|
const a = attrs;
|
|
13011
|
-
if (a.type ===
|
|
13299
|
+
if (a.type === import_types53.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
13012
13300
|
found = id;
|
|
13013
13301
|
}
|
|
13014
13302
|
});
|
|
@@ -13020,7 +13308,7 @@ function findMatchingRouteNode(graph, serviceName, method, path60) {
|
|
|
13020
13308
|
graph.forEachNode((id, attrs) => {
|
|
13021
13309
|
if (found) return;
|
|
13022
13310
|
const a = attrs;
|
|
13023
|
-
if (a.type !==
|
|
13311
|
+
if (a.type !== import_types53.NodeType.RouteNode || a.service !== serviceName) return;
|
|
13024
13312
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
13025
13313
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
13026
13314
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -13039,11 +13327,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
13039
13327
|
};
|
|
13040
13328
|
const mapping = config.workers?.[scriptName];
|
|
13041
13329
|
if (mapping) {
|
|
13042
|
-
const wholeFileId = (0,
|
|
13330
|
+
const wholeFileId = (0, import_types53.fileId)(mapping.service, mapping.entryFile);
|
|
13043
13331
|
return {
|
|
13044
13332
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
13045
13333
|
serviceName: mapping.service,
|
|
13046
|
-
edgeType:
|
|
13334
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
13047
13335
|
};
|
|
13048
13336
|
}
|
|
13049
13337
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -13052,13 +13340,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
13052
13340
|
return {
|
|
13053
13341
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
13054
13342
|
serviceName: fileNode.service,
|
|
13055
|
-
edgeType:
|
|
13343
|
+
edgeType: import_types53.EdgeType.CALLS
|
|
13056
13344
|
};
|
|
13057
13345
|
}
|
|
13058
13346
|
return {
|
|
13059
|
-
targetNodeId: (0,
|
|
13347
|
+
targetNodeId: (0, import_types53.infraId)("cloudflare-worker", scriptName),
|
|
13060
13348
|
serviceName: scriptName,
|
|
13061
|
-
edgeType:
|
|
13349
|
+
edgeType: import_types53.EdgeType.CALLS,
|
|
13062
13350
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
13063
13351
|
};
|
|
13064
13352
|
};
|
|
@@ -13254,14 +13542,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
13254
13542
|
|
|
13255
13543
|
// src/connectors/neon/resolve.ts
|
|
13256
13544
|
init_cjs_shims();
|
|
13257
|
-
var
|
|
13545
|
+
var import_types57 = require("@neat.is/types");
|
|
13258
13546
|
function createNeonResolveTarget(config) {
|
|
13259
13547
|
return (signal) => {
|
|
13260
13548
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
13261
13549
|
return {
|
|
13262
|
-
targetNodeId: (0,
|
|
13550
|
+
targetNodeId: (0, import_types57.infraId)("sql-table", signal.targetName),
|
|
13263
13551
|
serviceName: config.serviceName,
|
|
13264
|
-
edgeType:
|
|
13552
|
+
edgeType: import_types57.EdgeType.CALLS,
|
|
13265
13553
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
13266
13554
|
};
|
|
13267
13555
|
};
|
|
@@ -13299,6 +13587,412 @@ function createNeonConnector(config, deps = {}) {
|
|
|
13299
13587
|
};
|
|
13300
13588
|
}
|
|
13301
13589
|
|
|
13590
|
+
// src/connectors/cloud-run/index.ts
|
|
13591
|
+
init_cjs_shims();
|
|
13592
|
+
|
|
13593
|
+
// src/connectors/cloud-run/client.ts
|
|
13594
|
+
init_cjs_shims();
|
|
13595
|
+
function cloudRunRequestLogName(projectId) {
|
|
13596
|
+
return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
|
|
13597
|
+
}
|
|
13598
|
+
function buildCloudRunEntriesFilter(projectId, sinceIso) {
|
|
13599
|
+
return [
|
|
13600
|
+
`logName = "${cloudRunRequestLogName(projectId)}"`,
|
|
13601
|
+
`resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
|
|
13602
|
+
'httpRequest.requestMethod != ""',
|
|
13603
|
+
`timestamp >= "${sinceIso}"`
|
|
13604
|
+
].join(" AND ");
|
|
13605
|
+
}
|
|
13606
|
+
var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
|
|
13607
|
+
var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
|
|
13608
|
+
var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
|
|
13609
|
+
var PAGE_SIZE2 = 1e3;
|
|
13610
|
+
var MAX_PAGES2 = 20;
|
|
13611
|
+
async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
|
|
13612
|
+
const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
|
|
13613
|
+
const out = [];
|
|
13614
|
+
let pageToken;
|
|
13615
|
+
for (let page = 0; page < MAX_PAGES2; page++) {
|
|
13616
|
+
const body = {
|
|
13617
|
+
resourceNames: [`projects/${creds.projectId}`],
|
|
13618
|
+
filter,
|
|
13619
|
+
orderBy: "timestamp asc",
|
|
13620
|
+
pageSize: PAGE_SIZE2,
|
|
13621
|
+
...pageToken ? { pageToken } : {}
|
|
13622
|
+
};
|
|
13623
|
+
const res = await junctionFetch(
|
|
13624
|
+
apiUrl,
|
|
13625
|
+
{
|
|
13626
|
+
method: "POST",
|
|
13627
|
+
headers: {
|
|
13628
|
+
...bearerAuthHeader(creds.accessToken),
|
|
13629
|
+
"Content-Type": "application/json"
|
|
13630
|
+
},
|
|
13631
|
+
body: JSON.stringify(body)
|
|
13632
|
+
},
|
|
13633
|
+
// accountKey: the GCP project id — one customer's Cloud Logging quota is
|
|
13634
|
+
// scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
|
|
13635
|
+
// bucket), the same key Firebase's connector uses.
|
|
13636
|
+
{ provider: "cloud-run", accountKey: creds.projectId }
|
|
13637
|
+
);
|
|
13638
|
+
if (!res.ok) {
|
|
13639
|
+
throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
|
|
13640
|
+
}
|
|
13641
|
+
const json = await res.json();
|
|
13642
|
+
if (Array.isArray(json.entries)) out.push(...json.entries);
|
|
13643
|
+
if (!json.nextPageToken) break;
|
|
13644
|
+
pageToken = json.nextPageToken;
|
|
13645
|
+
}
|
|
13646
|
+
return out;
|
|
13647
|
+
}
|
|
13648
|
+
|
|
13649
|
+
// src/connectors/cloud-run/map.ts
|
|
13650
|
+
init_cjs_shims();
|
|
13651
|
+
|
|
13652
|
+
// src/connectors/cloud-run/types.ts
|
|
13653
|
+
init_cjs_shims();
|
|
13654
|
+
function readCloudRunCredentials(raw) {
|
|
13655
|
+
const projectId = raw["projectId"];
|
|
13656
|
+
const accessToken = raw["accessToken"];
|
|
13657
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
13658
|
+
throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
|
|
13659
|
+
}
|
|
13660
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
13661
|
+
throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
|
|
13662
|
+
}
|
|
13663
|
+
return { projectId, accessToken };
|
|
13664
|
+
}
|
|
13665
|
+
var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
|
|
13666
|
+
var FIELD_SEP2 = "\0";
|
|
13667
|
+
function packCloudRunTargetName(identity) {
|
|
13668
|
+
return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
|
|
13669
|
+
}
|
|
13670
|
+
function parseCloudRunTargetName(targetName) {
|
|
13671
|
+
const firstSep = targetName.indexOf(FIELD_SEP2);
|
|
13672
|
+
if (firstSep === -1) return null;
|
|
13673
|
+
const serviceName = targetName.slice(0, firstSep);
|
|
13674
|
+
const rest = targetName.slice(firstSep + 1);
|
|
13675
|
+
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
13676
|
+
if (secondSep === -1) return null;
|
|
13677
|
+
const method = rest.slice(0, secondSep);
|
|
13678
|
+
const path60 = rest.slice(secondSep + 1);
|
|
13679
|
+
if (!serviceName || !method || !path60) return null;
|
|
13680
|
+
return { serviceName, method, path: path60 };
|
|
13681
|
+
}
|
|
13682
|
+
|
|
13683
|
+
// src/connectors/cloud-run/map.ts
|
|
13684
|
+
var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
|
|
13685
|
+
function pathFromRequestUrl2(requestUrl) {
|
|
13686
|
+
if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
|
|
13687
|
+
if (requestUrl.startsWith("/")) {
|
|
13688
|
+
const withoutQuery = requestUrl.split("?")[0];
|
|
13689
|
+
return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
|
|
13690
|
+
}
|
|
13691
|
+
try {
|
|
13692
|
+
const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
|
|
13693
|
+
const parsed = new URL(candidate);
|
|
13694
|
+
return parsed.pathname || "/";
|
|
13695
|
+
} catch {
|
|
13696
|
+
return null;
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
var ERROR_STATUS_THRESHOLD4 = 500;
|
|
13700
|
+
function mapLogEntryToSignal2(entry) {
|
|
13701
|
+
if (!entry || typeof entry !== "object") return null;
|
|
13702
|
+
if (entry.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
|
|
13703
|
+
const serviceName = entry.resource?.labels?.["service_name"];
|
|
13704
|
+
if (typeof serviceName !== "string" || serviceName.length === 0) return null;
|
|
13705
|
+
const req = entry.httpRequest;
|
|
13706
|
+
if (!req) return null;
|
|
13707
|
+
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
13708
|
+
const method = req.requestMethod.toUpperCase();
|
|
13709
|
+
const path60 = pathFromRequestUrl2(req.requestUrl);
|
|
13710
|
+
if (path60 === null) return null;
|
|
13711
|
+
const timestamp = entry.timestamp;
|
|
13712
|
+
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
13713
|
+
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
13714
|
+
return {
|
|
13715
|
+
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
13716
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path60 }),
|
|
13717
|
+
callCount: 1,
|
|
13718
|
+
errorCount: isError ? 1 : 0,
|
|
13719
|
+
lastObservedIso: timestamp
|
|
13720
|
+
};
|
|
13721
|
+
}
|
|
13722
|
+
function mapLogEntriesToSignals2(entries) {
|
|
13723
|
+
const out = [];
|
|
13724
|
+
for (const entry of entries) {
|
|
13725
|
+
const signal = mapLogEntryToSignal2(entry);
|
|
13726
|
+
if (signal) out.push(signal);
|
|
13727
|
+
}
|
|
13728
|
+
return out;
|
|
13729
|
+
}
|
|
13730
|
+
|
|
13731
|
+
// src/connectors/cloud-run/resolve.ts
|
|
13732
|
+
init_cjs_shims();
|
|
13733
|
+
var import_types61 = require("@neat.is/types");
|
|
13734
|
+
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
13735
|
+
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
13736
|
+
let found = null;
|
|
13737
|
+
graph.forEachNode((_id, attrs) => {
|
|
13738
|
+
if (found) return;
|
|
13739
|
+
const node = attrs;
|
|
13740
|
+
if (node.type !== import_types61.NodeType.RouteNode) return;
|
|
13741
|
+
const route = attrs;
|
|
13742
|
+
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
13743
|
+
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
13744
|
+
const routeMethod = route.method.toUpperCase();
|
|
13745
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
13746
|
+
found = route.id;
|
|
13747
|
+
});
|
|
13748
|
+
return found;
|
|
13749
|
+
}
|
|
13750
|
+
function createCloudRunResolveTarget(graph, config) {
|
|
13751
|
+
return (signal) => {
|
|
13752
|
+
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
13753
|
+
const identity = parseCloudRunTargetName(signal.targetName);
|
|
13754
|
+
if (!identity) return null;
|
|
13755
|
+
const { serviceName: gcpServiceName, method, path: path60 } = identity;
|
|
13756
|
+
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
13757
|
+
if (mappedService) {
|
|
13758
|
+
const routeNodeId = findMatchingRouteNode2(
|
|
13759
|
+
graph,
|
|
13760
|
+
mappedService,
|
|
13761
|
+
method,
|
|
13762
|
+
normalizePathTemplate(path60)
|
|
13763
|
+
);
|
|
13764
|
+
if (routeNodeId) {
|
|
13765
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types61.EdgeType.CALLS };
|
|
13766
|
+
}
|
|
13767
|
+
}
|
|
13768
|
+
return {
|
|
13769
|
+
targetNodeId: (0, import_types61.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
13770
|
+
serviceName: mappedService ?? gcpServiceName,
|
|
13771
|
+
edgeType: import_types61.EdgeType.CALLS,
|
|
13772
|
+
ensureInfraNode: {
|
|
13773
|
+
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
13774
|
+
name: gcpServiceName,
|
|
13775
|
+
provider: "cloud-run"
|
|
13776
|
+
}
|
|
13777
|
+
};
|
|
13778
|
+
};
|
|
13779
|
+
}
|
|
13780
|
+
|
|
13781
|
+
// src/connectors/cloud-run/index.ts
|
|
13782
|
+
var CloudRunConnector = class {
|
|
13783
|
+
constructor(config = {}) {
|
|
13784
|
+
this.config = config;
|
|
13785
|
+
}
|
|
13786
|
+
config;
|
|
13787
|
+
provider = "cloud-run";
|
|
13788
|
+
async poll(ctx) {
|
|
13789
|
+
const creds = readCloudRunCredentials(ctx.credentials);
|
|
13790
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
|
|
13791
|
+
const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
13792
|
+
const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
|
|
13793
|
+
return mapLogEntriesToSignals2(entries);
|
|
13794
|
+
}
|
|
13795
|
+
};
|
|
13796
|
+
function boundedSinceIso(since, now, maxLookbackMs) {
|
|
13797
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13798
|
+
if (!since) return floor.toISOString();
|
|
13799
|
+
const sinceMs = new Date(since).getTime();
|
|
13800
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13801
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13802
|
+
}
|
|
13803
|
+
function createCloudRunConnector(graph, config = {}) {
|
|
13804
|
+
return {
|
|
13805
|
+
connector: new CloudRunConnector(config),
|
|
13806
|
+
resolveTarget: createCloudRunResolveTarget(graph, config)
|
|
13807
|
+
};
|
|
13808
|
+
}
|
|
13809
|
+
|
|
13810
|
+
// src/connectors/render/index.ts
|
|
13811
|
+
init_cjs_shims();
|
|
13812
|
+
var import_types64 = require("@neat.is/types");
|
|
13813
|
+
|
|
13814
|
+
// src/connectors/render/types.ts
|
|
13815
|
+
init_cjs_shims();
|
|
13816
|
+
function readRenderToken(credentials) {
|
|
13817
|
+
const token = credentials.token;
|
|
13818
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
13819
|
+
throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
|
|
13820
|
+
}
|
|
13821
|
+
return token;
|
|
13822
|
+
}
|
|
13823
|
+
function renderLabelValue(entry, name) {
|
|
13824
|
+
if (!Array.isArray(entry.labels)) return void 0;
|
|
13825
|
+
const label = entry.labels.find((l) => l && typeof l === "object" && l.name === name);
|
|
13826
|
+
return label && typeof label.value === "string" ? label.value : void 0;
|
|
13827
|
+
}
|
|
13828
|
+
|
|
13829
|
+
// src/connectors/render/client.ts
|
|
13830
|
+
init_cjs_shims();
|
|
13831
|
+
var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
|
|
13832
|
+
var DEFAULT_RENDER_LOG_LIMIT = 100;
|
|
13833
|
+
var RENDER_MAX_LOG_LIMIT = 100;
|
|
13834
|
+
var DEFAULT_RENDER_MAX_PAGES = 20;
|
|
13835
|
+
var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
|
|
13836
|
+
function clampLimit(limit) {
|
|
13837
|
+
const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
|
|
13838
|
+
if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
|
|
13839
|
+
return Math.min(raw, RENDER_MAX_LOG_LIMIT);
|
|
13840
|
+
}
|
|
13841
|
+
function boundedRenderStartTime(since, now, maxLookbackMs) {
|
|
13842
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
13843
|
+
if (!since) return floor.toISOString();
|
|
13844
|
+
const sinceMs = new Date(since).getTime();
|
|
13845
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
13846
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
13847
|
+
}
|
|
13848
|
+
async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
|
|
13849
|
+
const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
|
|
13850
|
+
url.searchParams.set("ownerId", config.ownerId);
|
|
13851
|
+
url.searchParams.set("resource", config.resourceId);
|
|
13852
|
+
url.searchParams.set("type", "request");
|
|
13853
|
+
url.searchParams.set("startTime", startTime);
|
|
13854
|
+
url.searchParams.set("endTime", endTime);
|
|
13855
|
+
url.searchParams.set("direction", "backward");
|
|
13856
|
+
url.searchParams.set("limit", String(limit));
|
|
13857
|
+
const res = await junctionFetch(
|
|
13858
|
+
url,
|
|
13859
|
+
{ method: "GET", headers: { ...bearerAuthHeader(token) } },
|
|
13860
|
+
{ provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
|
|
13861
|
+
);
|
|
13862
|
+
if (!res.ok) {
|
|
13863
|
+
throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
|
|
13864
|
+
}
|
|
13865
|
+
return await res.json();
|
|
13866
|
+
}
|
|
13867
|
+
async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
|
|
13868
|
+
const limit = clampLimit(config.limit);
|
|
13869
|
+
const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
|
|
13870
|
+
const out = [];
|
|
13871
|
+
let pageStart = startTime;
|
|
13872
|
+
let pageEnd = endTime;
|
|
13873
|
+
for (let page = 0; page < maxPages; page++) {
|
|
13874
|
+
const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
|
|
13875
|
+
if (Array.isArray(body.logs)) out.push(...body.logs);
|
|
13876
|
+
if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
|
|
13877
|
+
pageStart = body.nextStartTime;
|
|
13878
|
+
pageEnd = body.nextEndTime;
|
|
13879
|
+
}
|
|
13880
|
+
return out;
|
|
13881
|
+
}
|
|
13882
|
+
|
|
13883
|
+
// src/connectors/render/index.ts
|
|
13884
|
+
var ROUTE_TARGET_KIND2 = "route";
|
|
13885
|
+
var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
|
|
13886
|
+
function buildRenderRouteIndex(graph, serviceName) {
|
|
13887
|
+
const out = [];
|
|
13888
|
+
graph.forEachNode((_id, attrs) => {
|
|
13889
|
+
const node = attrs;
|
|
13890
|
+
if (node.type !== import_types64.NodeType.RouteNode) return;
|
|
13891
|
+
const route = attrs;
|
|
13892
|
+
if (route.service !== serviceName) return;
|
|
13893
|
+
out.push({
|
|
13894
|
+
method: route.method.toUpperCase(),
|
|
13895
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
13896
|
+
routeNodeId: route.id,
|
|
13897
|
+
path: route.path,
|
|
13898
|
+
line: route.line
|
|
13899
|
+
});
|
|
13900
|
+
});
|
|
13901
|
+
return out;
|
|
13902
|
+
}
|
|
13903
|
+
function findRenderRoute(entries, method, normalizedPath) {
|
|
13904
|
+
return entries.find(
|
|
13905
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
|
|
13906
|
+
);
|
|
13907
|
+
}
|
|
13908
|
+
function bucketKey3(method, normalizedPath) {
|
|
13909
|
+
return `${method} ${normalizedPath}`;
|
|
13910
|
+
}
|
|
13911
|
+
function isHttpErrorStatus2(status2) {
|
|
13912
|
+
return status2 >= 400;
|
|
13913
|
+
}
|
|
13914
|
+
function upsertBucket2(buckets2, key, isError, timestamp, build) {
|
|
13915
|
+
const existing = buckets2.get(key);
|
|
13916
|
+
if (existing) {
|
|
13917
|
+
existing.callCount += 1;
|
|
13918
|
+
if (isError) existing.errorCount += 1;
|
|
13919
|
+
if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
|
|
13920
|
+
return;
|
|
13921
|
+
}
|
|
13922
|
+
buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
|
|
13923
|
+
}
|
|
13924
|
+
function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
13925
|
+
const buckets2 = /* @__PURE__ */ new Map();
|
|
13926
|
+
if (!Array.isArray(entries)) return [];
|
|
13927
|
+
for (const entry of entries) {
|
|
13928
|
+
if (!entry || typeof entry !== "object") continue;
|
|
13929
|
+
if (typeof entry.timestamp !== "string") continue;
|
|
13930
|
+
const method = renderLabelValue(entry, "method");
|
|
13931
|
+
const rawPath = renderLabelValue(entry, "path");
|
|
13932
|
+
if (typeof method !== "string" || method.length === 0) continue;
|
|
13933
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) continue;
|
|
13934
|
+
const methodUpper = method.toUpperCase();
|
|
13935
|
+
const pathOnly = rawPath.split("?")[0];
|
|
13936
|
+
const normalizedPath = normalizePathTemplate(pathOnly);
|
|
13937
|
+
const statusCode = Number.parseInt(renderLabelValue(entry, "statusCode") ?? "", 10);
|
|
13938
|
+
const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
|
|
13939
|
+
const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
|
|
13940
|
+
if (match) {
|
|
13941
|
+
upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry.timestamp, () => ({
|
|
13942
|
+
targetKind: ROUTE_TARGET_KIND2,
|
|
13943
|
+
targetName: match.routeNodeId,
|
|
13944
|
+
// RouteNode.line is optional in the schema (packages/types/src/
|
|
13945
|
+
// nodes.ts) even though routes.ts always sets it today — skip the
|
|
13946
|
+
// callSite rather than fabricate a line when it's ever absent
|
|
13947
|
+
// (file-awareness.md §6).
|
|
13948
|
+
...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
|
|
13949
|
+
}));
|
|
13950
|
+
} else {
|
|
13951
|
+
upsertBucket2(
|
|
13952
|
+
buckets2,
|
|
13953
|
+
`unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
|
|
13954
|
+
isError,
|
|
13955
|
+
entry.timestamp,
|
|
13956
|
+
() => ({
|
|
13957
|
+
targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
|
|
13958
|
+
targetName: bucketKey3(methodUpper, normalizedPath)
|
|
13959
|
+
})
|
|
13960
|
+
);
|
|
13961
|
+
}
|
|
13962
|
+
}
|
|
13963
|
+
return [...buckets2.values()].map((b) => ({
|
|
13964
|
+
targetKind: b.targetKind,
|
|
13965
|
+
targetName: b.targetName,
|
|
13966
|
+
callCount: b.callCount,
|
|
13967
|
+
errorCount: b.errorCount,
|
|
13968
|
+
lastObservedIso: b.lastObservedIso,
|
|
13969
|
+
...b.callSite ? { callSite: b.callSite } : {}
|
|
13970
|
+
}));
|
|
13971
|
+
}
|
|
13972
|
+
function createRenderResolveTarget(config) {
|
|
13973
|
+
return (signal) => {
|
|
13974
|
+
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
13975
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
13976
|
+
}
|
|
13977
|
+
return null;
|
|
13978
|
+
};
|
|
13979
|
+
}
|
|
13980
|
+
function createRenderConnector(graph, config) {
|
|
13981
|
+
return {
|
|
13982
|
+
provider: "render",
|
|
13983
|
+
async poll(ctx) {
|
|
13984
|
+
const token = readRenderToken(ctx.credentials);
|
|
13985
|
+
const now = /* @__PURE__ */ new Date();
|
|
13986
|
+
const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
|
|
13987
|
+
const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
|
|
13988
|
+
const endTime = now.toISOString();
|
|
13989
|
+
const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
|
|
13990
|
+
const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
|
|
13991
|
+
return mapRenderRequestLogsToSignals(logs, routeIndex);
|
|
13992
|
+
}
|
|
13993
|
+
};
|
|
13994
|
+
}
|
|
13995
|
+
|
|
13302
13996
|
// src/connectors/registry.ts
|
|
13303
13997
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
13304
13998
|
async function authProbe(input) {
|
|
@@ -13474,6 +14168,71 @@ var PROVIDER_DISPATCH = {
|
|
|
13474
14168
|
return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
|
|
13475
14169
|
}
|
|
13476
14170
|
}
|
|
14171
|
+
},
|
|
14172
|
+
"cloud-run": {
|
|
14173
|
+
provider: "cloud-run",
|
|
14174
|
+
// Cloud Run reads both projectId and accessToken from the credential; the
|
|
14175
|
+
// single-string form maps to the secret (the token), and the required-fields
|
|
14176
|
+
// check below catches a projectId that was never supplied.
|
|
14177
|
+
primaryCredentialKey: "accessToken",
|
|
14178
|
+
requiredCredentialFields: ["projectId", "accessToken"],
|
|
14179
|
+
requiredOptionFields: [],
|
|
14180
|
+
build(graph, options) {
|
|
14181
|
+
return createCloudRunConnector(graph, options);
|
|
14182
|
+
},
|
|
14183
|
+
// POST entries:list with pageSize 1 — the exact surface poll() reads, so the
|
|
14184
|
+
// probe checks the actual `logging.logEntries.list` permission the connector
|
|
14185
|
+
// needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
|
|
14186
|
+
// instead check `logging.logs.list`, falsely rejecting a correctly-scoped
|
|
14187
|
+
// custom role that carries only `logging.logEntries.list` (the narrowest
|
|
14188
|
+
// grant docs/connectors/cloud-run.md documents) — the same false-negative
|
|
14189
|
+
// trap Railway's validate avoids by probing its real query. A 2xx means the
|
|
14190
|
+
// token can list log entries; 401/403 means the provider rejected it.
|
|
14191
|
+
validate({ credentials, fetchImpl }) {
|
|
14192
|
+
const projectId = String(credentials.projectId ?? "");
|
|
14193
|
+
return authProbe({
|
|
14194
|
+
provider: "cloud-run",
|
|
14195
|
+
accountKey: projectId || "validate",
|
|
14196
|
+
url: "https://logging.googleapis.com/v2/entries:list",
|
|
14197
|
+
token: String(credentials.accessToken ?? ""),
|
|
14198
|
+
init: {
|
|
14199
|
+
method: "POST",
|
|
14200
|
+
headers: { "Content-Type": "application/json" },
|
|
14201
|
+
body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
|
|
14202
|
+
},
|
|
14203
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14204
|
+
});
|
|
14205
|
+
}
|
|
14206
|
+
},
|
|
14207
|
+
render: {
|
|
14208
|
+
provider: "render",
|
|
14209
|
+
primaryCredentialKey: "token",
|
|
14210
|
+
requiredCredentialFields: ["token"],
|
|
14211
|
+
requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
|
|
14212
|
+
build(graph, options) {
|
|
14213
|
+
const config = options;
|
|
14214
|
+
return {
|
|
14215
|
+
connector: createRenderConnector(graph, config),
|
|
14216
|
+
resolveTarget: createRenderResolveTarget(config)
|
|
14217
|
+
};
|
|
14218
|
+
},
|
|
14219
|
+
// GET /v1/services?limit=1 — the cheapest read the Render API key
|
|
14220
|
+
// authenticates against (render.com/docs/api). Unlike Railway's GraphQL
|
|
14221
|
+
// gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
|
|
14222
|
+
// 401/403, so authProbe's status-code check is a true verdict here. The
|
|
14223
|
+
// logs query itself also needs an ownerId + resource; `services` needs
|
|
14224
|
+
// neither and still fails 401 on a bad token, so it's the honest probe.
|
|
14225
|
+
validate({ credentials, options, fetchImpl }) {
|
|
14226
|
+
const cfg = options;
|
|
14227
|
+
const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
|
|
14228
|
+
return authProbe({
|
|
14229
|
+
provider: "render",
|
|
14230
|
+
accountKey: cfg.ownerId ?? "validate",
|
|
14231
|
+
url: `${baseUrl}/services?limit=1`,
|
|
14232
|
+
token: String(credentials.token ?? ""),
|
|
14233
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14234
|
+
});
|
|
14235
|
+
}
|
|
13477
14236
|
}
|
|
13478
14237
|
};
|
|
13479
14238
|
function vercelCredsFrom(credentials) {
|
|
@@ -13825,11 +14584,11 @@ function registerRoutes(scope, ctx) {
|
|
|
13825
14584
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
13826
14585
|
const parsed = [];
|
|
13827
14586
|
for (const c of candidates) {
|
|
13828
|
-
const r =
|
|
14587
|
+
const r = import_types66.DivergenceTypeSchema.safeParse(c);
|
|
13829
14588
|
if (!r.success) {
|
|
13830
14589
|
return reply.code(400).send({
|
|
13831
14590
|
error: `unknown divergence type "${c}"`,
|
|
13832
|
-
allowed:
|
|
14591
|
+
allowed: import_types66.DivergenceTypeSchema.options
|
|
13833
14592
|
});
|
|
13834
14593
|
}
|
|
13835
14594
|
parsed.push(r.data);
|
|
@@ -14138,7 +14897,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14138
14897
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
14139
14898
|
let violations = await log.readAll();
|
|
14140
14899
|
if (req.query.severity) {
|
|
14141
|
-
const sev =
|
|
14900
|
+
const sev = import_types66.PolicySeveritySchema.safeParse(req.query.severity);
|
|
14142
14901
|
if (!sev.success) {
|
|
14143
14902
|
return reply.code(400).send({
|
|
14144
14903
|
error: "invalid severity",
|
|
@@ -14177,7 +14936,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14177
14936
|
scope.post("/policies/check", async (req, reply) => {
|
|
14178
14937
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
14179
14938
|
if (!proj) return;
|
|
14180
|
-
const parsed =
|
|
14939
|
+
const parsed = import_types66.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
14181
14940
|
if (!parsed.success) {
|
|
14182
14941
|
return reply.code(400).send({
|
|
14183
14942
|
error: "invalid /policies/check body",
|
|
@@ -14526,7 +15285,7 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
14526
15285
|
}
|
|
14527
15286
|
|
|
14528
15287
|
// src/daemon.ts
|
|
14529
|
-
var
|
|
15288
|
+
var import_types67 = require("@neat.is/types");
|
|
14530
15289
|
function daemonJsonPath(scanPath) {
|
|
14531
15290
|
return import_node_path59.default.join(scanPath, "neat-out", "daemon.json");
|
|
14532
15291
|
}
|
|
@@ -14651,7 +15410,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
14651
15410
|
if (!serviceName) return true;
|
|
14652
15411
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
14653
15412
|
return graph.someNode(
|
|
14654
|
-
(_id, attrs) => attrs.type ===
|
|
15413
|
+
(_id, attrs) => attrs.type === import_types67.NodeType.ServiceNode && attrs.name === serviceName
|
|
14655
15414
|
);
|
|
14656
15415
|
}
|
|
14657
15416
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|