@neat.is/core 0.7.8 → 0.7.10
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-LN75Z624.js → chunk-6T7ZHODF.js} +554 -65
- package/dist/chunk-6T7ZHODF.js.map +1 -0
- package/dist/{chunk-BIY46Q6U.js → chunk-BC53SCT7.js} +36 -2
- package/dist/chunk-BC53SCT7.js.map +1 -0
- package/dist/{chunk-OVRVDSUO.js → chunk-D4OX6MUX.js} +58 -4
- package/dist/chunk-D4OX6MUX.js.map +1 -0
- package/dist/{chunk-4GCV46AP.js → chunk-FCO5Z3RW.js} +2 -2
- package/dist/{chunk-Y43UCVZS.js → chunk-UUYCTH2E.js} +24 -8
- package/dist/chunk-UUYCTH2E.js.map +1 -0
- package/dist/cli.cjs +1392 -314
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +704 -176
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +645 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +645 -77
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-DPSAGRUU.js → otel-grpc-CO47JRIN.js} +3 -3
- package/dist/server.cjs +618 -74
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-BIY46Q6U.js.map +0 -1
- package/dist/chunk-LN75Z624.js.map +0 -1
- package/dist/chunk-OVRVDSUO.js.map +0 -1
- package/dist/chunk-Y43UCVZS.js.map +0 -1
- /package/dist/{chunk-4GCV46AP.js.map → chunk-FCO5Z3RW.js.map} +0 -0
- /package/dist/{otel-grpc-DPSAGRUU.js.map → otel-grpc-CO47JRIN.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -504,13 +504,18 @@ function loadProtobufResponseEncoder() {
|
|
|
504
504
|
);
|
|
505
505
|
return exportTraceServiceResponseType;
|
|
506
506
|
}
|
|
507
|
-
function encodeProtobufResponseBody() {
|
|
508
|
-
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
507
|
+
function encodeProtobufResponseBody(rejected, message) {
|
|
509
508
|
const Type = loadProtobufResponseEncoder();
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
509
|
+
if (!rejected) {
|
|
510
|
+
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
511
|
+
const msg2 = Type.create({});
|
|
512
|
+
cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
|
|
513
|
+
return cachedProtobufResponseBody;
|
|
514
|
+
}
|
|
515
|
+
const msg = Type.fromObject({
|
|
516
|
+
partial_success: { rejected_spans: rejected, error_message: message ?? "" }
|
|
517
|
+
});
|
|
518
|
+
return Buffer.from(Type.encode(msg).finish());
|
|
514
519
|
}
|
|
515
520
|
async function decodeProtobufBody(buf) {
|
|
516
521
|
const Type = loadProtobufDecoder();
|
|
@@ -625,6 +630,13 @@ async function buildOtelReceiver(opts) {
|
|
|
625
630
|
}
|
|
626
631
|
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
627
632
|
}
|
|
633
|
+
function sendOtlpPartial(reply, flavor, rejected, message) {
|
|
634
|
+
if (flavor === "protobuf") {
|
|
635
|
+
const buf = encodeProtobufResponseBody(rejected, message);
|
|
636
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
637
|
+
}
|
|
638
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
|
|
639
|
+
}
|
|
628
640
|
app.addContentTypeParser(
|
|
629
641
|
"application/x-protobuf",
|
|
630
642
|
{ parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
|
|
@@ -652,6 +664,10 @@ async function buildOtelReceiver(opts) {
|
|
|
652
664
|
}
|
|
653
665
|
}
|
|
654
666
|
enqueue(spans);
|
|
667
|
+
if (opts.classifyBareRoutability) {
|
|
668
|
+
const { rejected, message } = opts.classifyBareRoutability(spans);
|
|
669
|
+
if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
|
|
670
|
+
}
|
|
655
671
|
return sendOtlpSuccess(reply, result.flavor);
|
|
656
672
|
});
|
|
657
673
|
app.post("/projects/:project/v1/traces", async (req2, reply) => {
|
|
@@ -2213,6 +2229,7 @@ var import_yaml = require("yaml");
|
|
|
2213
2229
|
var import_types3 = require("@neat.is/types");
|
|
2214
2230
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2215
2231
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2232
|
+
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
2216
2233
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2217
2234
|
"node_modules",
|
|
2218
2235
|
".git",
|
|
@@ -2252,6 +2269,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2252
2269
|
function isConfigFile(name) {
|
|
2253
2270
|
const ext = import_node_path3.default.extname(name);
|
|
2254
2271
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2272
|
+
if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
|
|
2255
2273
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2256
2274
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2257
2275
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -4828,10 +4846,15 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4828
4846
|
}
|
|
4829
4847
|
if (!entry2) return null;
|
|
4830
4848
|
try {
|
|
4831
|
-
const
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4849
|
+
const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
|
|
4850
|
+
let pos = entry2.consumer.originalPositionFor({ line: queryLine, column: 0 });
|
|
4851
|
+
if (!pos || !pos.source) {
|
|
4852
|
+
pos = entry2.consumer.originalPositionFor({
|
|
4853
|
+
line: queryLine,
|
|
4854
|
+
column: 0,
|
|
4855
|
+
bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
|
|
4856
|
+
});
|
|
4857
|
+
}
|
|
4835
4858
|
if (!pos || !pos.source) return null;
|
|
4836
4859
|
const root = entry2.consumer.sourceRoot ?? "";
|
|
4837
4860
|
const resolved = import_node_path8.default.resolve(entry2.dir, root, pos.source);
|
|
@@ -4840,6 +4863,9 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4840
4863
|
return null;
|
|
4841
4864
|
}
|
|
4842
4865
|
}
|
|
4866
|
+
function hasAdjacentSourceMap(absFilepath) {
|
|
4867
|
+
return sourceMapCache.get(absFilepath) != null;
|
|
4868
|
+
}
|
|
4843
4869
|
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
4844
4870
|
const filepath = codeFilepathOf(span.attributes);
|
|
4845
4871
|
if (filepath === void 0) return null;
|
|
@@ -4855,7 +4881,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
4855
4881
|
}
|
|
4856
4882
|
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
4857
4883
|
if (!relPath) return null;
|
|
4858
|
-
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
4884
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
|
|
4859
4885
|
warnNoSourceMaps(serviceNode.name);
|
|
4860
4886
|
}
|
|
4861
4887
|
const fn = codeFunctionOf(span.attributes);
|
|
@@ -5131,7 +5157,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
5131
5157
|
function frontierIdFor(host) {
|
|
5132
5158
|
return (0, import_types8.frontierId)(host);
|
|
5133
5159
|
}
|
|
5134
|
-
function
|
|
5160
|
+
function resolveFusedServiceId(graph, serviceName, env) {
|
|
5135
5161
|
const id = (0, import_types8.serviceId)(serviceName, env);
|
|
5136
5162
|
if (graph.hasNode(id)) return id;
|
|
5137
5163
|
const wanted = serviceName.toLowerCase();
|
|
@@ -5141,17 +5167,21 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
5141
5167
|
if (svc.discoveredVia === "otel") return false;
|
|
5142
5168
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
5143
5169
|
});
|
|
5144
|
-
|
|
5170
|
+
return extractedId ?? id;
|
|
5171
|
+
}
|
|
5172
|
+
function ensureServiceNode(graph, serviceName, env) {
|
|
5173
|
+
const resolved = resolveFusedServiceId(graph, serviceName, env);
|
|
5174
|
+
if (graph.hasNode(resolved)) return resolved;
|
|
5145
5175
|
const node = {
|
|
5146
|
-
id,
|
|
5176
|
+
id: resolved,
|
|
5147
5177
|
type: import_types8.NodeType.ServiceNode,
|
|
5148
5178
|
name: serviceName,
|
|
5149
5179
|
language: "unknown",
|
|
5150
5180
|
discoveredVia: "otel",
|
|
5151
5181
|
...env !== "unknown" ? { env } : {}
|
|
5152
5182
|
};
|
|
5153
|
-
graph.addNode(
|
|
5154
|
-
return
|
|
5183
|
+
graph.addNode(resolved, node);
|
|
5184
|
+
return resolved;
|
|
5155
5185
|
}
|
|
5156
5186
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
5157
5187
|
const id = (0, import_types8.infraId)(kind, name);
|
|
@@ -5344,8 +5374,23 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5344
5374
|
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5345
5375
|
await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5346
5376
|
}
|
|
5377
|
+
async function appendConnectorIncident(errorsPath, input) {
|
|
5378
|
+
const ev = {
|
|
5379
|
+
id: input.id,
|
|
5380
|
+
timestamp: input.timestamp,
|
|
5381
|
+
service: input.service,
|
|
5382
|
+
traceId: input.id,
|
|
5383
|
+
spanId: input.id,
|
|
5384
|
+
errorType: input.errorType,
|
|
5385
|
+
errorMessage: input.errorMessage,
|
|
5386
|
+
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
5387
|
+
affectedNode: input.affectedNode
|
|
5388
|
+
};
|
|
5389
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
|
|
5390
|
+
await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5391
|
+
}
|
|
5347
5392
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5348
|
-
const sid = (0, import_types8.serviceId)(span.service, span.env);
|
|
5393
|
+
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
|
|
5349
5394
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
5350
5395
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
5351
5396
|
if (callSite) {
|
|
@@ -6744,7 +6789,7 @@ function disambiguate(defs) {
|
|
|
6744
6789
|
}
|
|
6745
6790
|
async function addSymbols(graph, services) {
|
|
6746
6791
|
const parsers = /* @__PURE__ */ new Map();
|
|
6747
|
-
const
|
|
6792
|
+
const parserForExt5 = (ext) => {
|
|
6748
6793
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6749
6794
|
if (!grammar) return null;
|
|
6750
6795
|
let parser = parsers.get(ext);
|
|
@@ -6760,7 +6805,7 @@ async function addSymbols(graph, services) {
|
|
|
6760
6805
|
for (const service of services) {
|
|
6761
6806
|
const files = await loadSourceFiles(service.dir);
|
|
6762
6807
|
for (const file of files) {
|
|
6763
|
-
const parser =
|
|
6808
|
+
const parser = parserForExt5(import_node_path17.default.extname(file.path));
|
|
6764
6809
|
if (!parser) continue;
|
|
6765
6810
|
const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
|
|
6766
6811
|
let defs;
|
|
@@ -6912,7 +6957,7 @@ function stringInner(node) {
|
|
|
6912
6957
|
}
|
|
6913
6958
|
async function addSymbolEdges(graph, services) {
|
|
6914
6959
|
const parsers = /* @__PURE__ */ new Map();
|
|
6915
|
-
const
|
|
6960
|
+
const parserForExt5 = (ext) => {
|
|
6916
6961
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6917
6962
|
if (!grammar) return null;
|
|
6918
6963
|
let parser = parsers.get(ext);
|
|
@@ -6929,7 +6974,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6929
6974
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6930
6975
|
const files = await loadSourceFiles(service.dir);
|
|
6931
6976
|
for (const file of files) {
|
|
6932
|
-
const parser =
|
|
6977
|
+
const parser = parserForExt5(import_node_path18.default.extname(file.path));
|
|
6933
6978
|
if (!parser) continue;
|
|
6934
6979
|
const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
|
|
6935
6980
|
const fileDir = import_node_path18.default.dirname(file.path);
|
|
@@ -7191,7 +7236,7 @@ function firstReferenceLines(root, wanted) {
|
|
|
7191
7236
|
}
|
|
7192
7237
|
async function addServerActions(graph, services) {
|
|
7193
7238
|
const parsers = /* @__PURE__ */ new Map();
|
|
7194
|
-
const
|
|
7239
|
+
const parserForExt5 = (ext) => {
|
|
7195
7240
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
7196
7241
|
if (!grammar) return null;
|
|
7197
7242
|
let parser = parsers.get(ext);
|
|
@@ -7214,7 +7259,7 @@ async function addServerActions(graph, services) {
|
|
|
7214
7259
|
const files = await loadSourceFiles(service.dir);
|
|
7215
7260
|
for (const file of files) {
|
|
7216
7261
|
if (isTestPath(file.path)) continue;
|
|
7217
|
-
const parser =
|
|
7262
|
+
const parser = parserForExt5(import_node_path19.default.extname(file.path));
|
|
7218
7263
|
if (!parser) continue;
|
|
7219
7264
|
const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
|
|
7220
7265
|
let root;
|
|
@@ -7277,7 +7322,7 @@ async function addServerActions(graph, services) {
|
|
|
7277
7322
|
}
|
|
7278
7323
|
for (const file of files) {
|
|
7279
7324
|
if (isTestPath(file.path)) continue;
|
|
7280
|
-
const parser =
|
|
7325
|
+
const parser = parserForExt5(import_node_path19.default.extname(file.path));
|
|
7281
7326
|
if (!parser) continue;
|
|
7282
7327
|
const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
|
|
7283
7328
|
const fileDir = import_node_path19.default.dirname(file.path);
|
|
@@ -8241,6 +8286,7 @@ init_cjs_shims();
|
|
|
8241
8286
|
var import_node_path30 = __toESM(require("path"), 1);
|
|
8242
8287
|
var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
|
|
8243
8288
|
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
8289
|
+
var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
|
|
8244
8290
|
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
8245
8291
|
var import_types20 = require("@neat.is/types");
|
|
8246
8292
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -8291,19 +8337,27 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
8291
8337
|
}
|
|
8292
8338
|
return out;
|
|
8293
8339
|
}
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8340
|
+
var GRAMMAR_BY_EXT2 = {
|
|
8341
|
+
".ts": import_tree_sitter_typescript2.default.typescript,
|
|
8342
|
+
".tsx": import_tree_sitter_typescript2.default.tsx,
|
|
8343
|
+
".js": import_tree_sitter_javascript4.default,
|
|
8344
|
+
".jsx": import_tree_sitter_javascript4.default,
|
|
8345
|
+
".mjs": import_tree_sitter_javascript4.default,
|
|
8346
|
+
".cjs": import_tree_sitter_javascript4.default,
|
|
8347
|
+
".py": import_tree_sitter_python3.default
|
|
8348
|
+
};
|
|
8349
|
+
function parserForExt(ext, cache) {
|
|
8350
|
+
const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
|
|
8351
|
+
let parser = cache.get(grammar);
|
|
8352
|
+
if (!parser) {
|
|
8353
|
+
parser = new import_tree_sitter6.default();
|
|
8354
|
+
parser.setLanguage(grammar);
|
|
8355
|
+
cache.set(grammar, parser);
|
|
8356
|
+
}
|
|
8357
|
+
return parser;
|
|
8303
8358
|
}
|
|
8304
8359
|
async function addHttpCallEdges(graph, services) {
|
|
8305
|
-
const
|
|
8306
|
-
const pyParser = makePyParser3();
|
|
8360
|
+
const parserCache = /* @__PURE__ */ new Map();
|
|
8307
8361
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8308
8362
|
let nodesAdded = 0;
|
|
8309
8363
|
let edgesAdded = 0;
|
|
@@ -8312,7 +8366,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8312
8366
|
const seen = /* @__PURE__ */ new Set();
|
|
8313
8367
|
for (const file of files) {
|
|
8314
8368
|
if (isTestPath(file.path)) continue;
|
|
8315
|
-
const parser = import_node_path30.default.extname(file.path)
|
|
8369
|
+
const parser = parserForExt(import_node_path30.default.extname(file.path), parserCache);
|
|
8316
8370
|
let sites;
|
|
8317
8371
|
try {
|
|
8318
8372
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8385,7 +8439,7 @@ function parseSource5(parser, source) {
|
|
|
8385
8439
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
|
|
8386
8440
|
);
|
|
8387
8441
|
}
|
|
8388
|
-
function
|
|
8442
|
+
function makeJsParser3() {
|
|
8389
8443
|
const p = new import_tree_sitter7.default();
|
|
8390
8444
|
p.setLanguage(import_tree_sitter_javascript5.default);
|
|
8391
8445
|
return p;
|
|
@@ -8563,7 +8617,7 @@ function findRoute(entries, method, normalizedPath) {
|
|
|
8563
8617
|
);
|
|
8564
8618
|
}
|
|
8565
8619
|
async function addRouteCallEdges(graph, services) {
|
|
8566
|
-
const jsParser =
|
|
8620
|
+
const jsParser = makeJsParser3();
|
|
8567
8621
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8568
8622
|
const routeIndex = buildRouteIndex(graph);
|
|
8569
8623
|
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
@@ -8955,7 +9009,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
|
|
|
8955
9009
|
var import_types27 = require("@neat.is/types");
|
|
8956
9010
|
var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
|
|
8957
9011
|
var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
|
|
8958
|
-
function
|
|
9012
|
+
function parserForExt2(ext) {
|
|
8959
9013
|
const p = new import_tree_sitter8.default();
|
|
8960
9014
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
|
|
8961
9015
|
return p;
|
|
@@ -9120,7 +9174,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9120
9174
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
9121
9175
|
if (!hasClient && !hasAdmin) return [];
|
|
9122
9176
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
9123
|
-
const tree = parseSource3(
|
|
9177
|
+
const tree = parseSource3(parserForExt2(import_node_path37.default.extname(file.path)), file.content);
|
|
9124
9178
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
9125
9179
|
const collLine = /* @__PURE__ */ new Map();
|
|
9126
9180
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -9537,7 +9591,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9537
9591
|
var import_types29 = require("@neat.is/types");
|
|
9538
9592
|
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
9539
9593
|
var PARSE_CHUNK6 = 16384;
|
|
9540
|
-
function
|
|
9594
|
+
function makePyParser3() {
|
|
9541
9595
|
const p = new import_tree_sitter9.default();
|
|
9542
9596
|
p.setLanguage(import_tree_sitter_python4.default);
|
|
9543
9597
|
return p;
|
|
@@ -9644,7 +9698,7 @@ function foreignKeyParentTable(call) {
|
|
|
9644
9698
|
}
|
|
9645
9699
|
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
9646
9700
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9647
|
-
const tree = parseSource6(
|
|
9701
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9648
9702
|
const out = [];
|
|
9649
9703
|
const seen = /* @__PURE__ */ new Set();
|
|
9650
9704
|
walk3(tree.rootNode, (node) => {
|
|
@@ -9681,7 +9735,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
9681
9735
|
}
|
|
9682
9736
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
9683
9737
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9684
|
-
const tree = parseSource6(
|
|
9738
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9685
9739
|
const out = [];
|
|
9686
9740
|
const seen = /* @__PURE__ */ new Set();
|
|
9687
9741
|
const push = (name, line, columns) => {
|
|
@@ -9733,7 +9787,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
9733
9787
|
function buildSqlalchemyModelRegistry(files) {
|
|
9734
9788
|
const table = /* @__PURE__ */ new Map();
|
|
9735
9789
|
const ambiguous = /* @__PURE__ */ new Set();
|
|
9736
|
-
const parser =
|
|
9790
|
+
const parser = makePyParser3();
|
|
9737
9791
|
for (const file of files) {
|
|
9738
9792
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
|
|
9739
9793
|
const tree = parseSource6(parser, file.content);
|
|
@@ -9782,7 +9836,7 @@ function importsModelName(content, name) {
|
|
|
9782
9836
|
function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
9783
9837
|
const registry = buildSqlalchemyModelRegistry(files);
|
|
9784
9838
|
if (registry.size === 0) return [];
|
|
9785
|
-
const parser =
|
|
9839
|
+
const parser = makePyParser3();
|
|
9786
9840
|
const out = [];
|
|
9787
9841
|
const seen = /* @__PURE__ */ new Set();
|
|
9788
9842
|
for (const file of files) {
|
|
@@ -9820,7 +9874,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9820
9874
|
var import_types30 = require("@neat.is/types");
|
|
9821
9875
|
var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
|
|
9822
9876
|
var PARSE_CHUNK7 = 16384;
|
|
9823
|
-
function
|
|
9877
|
+
function makePyParser4() {
|
|
9824
9878
|
const p = new import_tree_sitter10.default();
|
|
9825
9879
|
p.setLanguage(import_tree_sitter_python5.default);
|
|
9826
9880
|
return p;
|
|
@@ -9881,7 +9935,7 @@ function readMeta(body) {
|
|
|
9881
9935
|
}
|
|
9882
9936
|
function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
9883
9937
|
if (!DJANGO_IMPORT_RE.test(file.content)) return [];
|
|
9884
|
-
const tree = parseSource7(
|
|
9938
|
+
const tree = parseSource7(makePyParser4(), file.content);
|
|
9885
9939
|
const out = [];
|
|
9886
9940
|
const seen = /* @__PURE__ */ new Set();
|
|
9887
9941
|
const defaultAppLabel = import_node_path40.default.basename(import_node_path40.default.dirname(file.path));
|
|
@@ -9916,7 +9970,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
|
|
|
9916
9970
|
var import_types31 = require("@neat.is/types");
|
|
9917
9971
|
var DRIZZLE_IMPORT_RE = /drizzle-orm/;
|
|
9918
9972
|
var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
|
|
9919
|
-
function
|
|
9973
|
+
function parserForExt3(ext) {
|
|
9920
9974
|
const p = new import_tree_sitter11.default();
|
|
9921
9975
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
|
|
9922
9976
|
return p;
|
|
@@ -9988,7 +10042,7 @@ function columnsFromObject(obj) {
|
|
|
9988
10042
|
}
|
|
9989
10043
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
9990
10044
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9991
|
-
const tree = parseSource3(
|
|
10045
|
+
const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
|
|
9992
10046
|
const out = [];
|
|
9993
10047
|
const seen = /* @__PURE__ */ new Set();
|
|
9994
10048
|
const walk9 = (node) => {
|
|
@@ -10077,7 +10131,7 @@ function referencesTargetVar(call) {
|
|
|
10077
10131
|
}
|
|
10078
10132
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10079
10133
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10080
|
-
const tree = parseSource3(
|
|
10134
|
+
const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
|
|
10081
10135
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10082
10136
|
const out = [];
|
|
10083
10137
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11145,8 +11199,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11145
11199
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11146
11200
|
var import_types35 = require("@neat.is/types");
|
|
11147
11201
|
init_otel();
|
|
11148
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11149
11202
|
var PARSE_CHUNK10 = 16384;
|
|
11203
|
+
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
11204
|
+
"Query",
|
|
11205
|
+
"QueryContext",
|
|
11206
|
+
"QueryRow",
|
|
11207
|
+
"QueryRowContext",
|
|
11208
|
+
"Exec",
|
|
11209
|
+
"ExecContext",
|
|
11210
|
+
"Prepare",
|
|
11211
|
+
"PrepareContext"
|
|
11212
|
+
]);
|
|
11213
|
+
var SQLX_METHODS = /* @__PURE__ */ new Set([
|
|
11214
|
+
"Get",
|
|
11215
|
+
"Select",
|
|
11216
|
+
"Queryx",
|
|
11217
|
+
"QueryRowx",
|
|
11218
|
+
"NamedExec",
|
|
11219
|
+
"NamedQuery",
|
|
11220
|
+
"MustExec",
|
|
11221
|
+
"Preparex",
|
|
11222
|
+
"GetContext",
|
|
11223
|
+
"SelectContext"
|
|
11224
|
+
]);
|
|
11225
|
+
var DATABASE_SQL_IMPORT = "database/sql";
|
|
11226
|
+
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
11227
|
+
function makeGoParser3() {
|
|
11228
|
+
const p = new import_tree_sitter14.default();
|
|
11229
|
+
p.setLanguage(import_tree_sitter_go3.default);
|
|
11230
|
+
return p;
|
|
11231
|
+
}
|
|
11232
|
+
function parseSource10(parser, source) {
|
|
11233
|
+
return parser.parse(
|
|
11234
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK10)
|
|
11235
|
+
);
|
|
11236
|
+
}
|
|
11150
11237
|
function walk7(node, visit) {
|
|
11151
11238
|
visit(node);
|
|
11152
11239
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11154,25 +11241,54 @@ function walk7(node, visit) {
|
|
|
11154
11241
|
if (child) walk7(child, visit);
|
|
11155
11242
|
}
|
|
11156
11243
|
}
|
|
11244
|
+
function goStringLiteralValue(node) {
|
|
11245
|
+
if (!node) return null;
|
|
11246
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
11247
|
+
const t = node.text;
|
|
11248
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
11249
|
+
}
|
|
11250
|
+
return null;
|
|
11251
|
+
}
|
|
11252
|
+
function goImportsAny(root, names) {
|
|
11253
|
+
let found = false;
|
|
11254
|
+
walk7(root, (node) => {
|
|
11255
|
+
if (found || node.type !== "import_spec") return;
|
|
11256
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
11257
|
+
const value = goStringLiteralValue(node.namedChild(i));
|
|
11258
|
+
if (value !== null && names.has(value)) found = true;
|
|
11259
|
+
}
|
|
11260
|
+
});
|
|
11261
|
+
return found;
|
|
11262
|
+
}
|
|
11263
|
+
function firstStringLiteralArg(argsNode) {
|
|
11264
|
+
if (!argsNode) return null;
|
|
11265
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
11266
|
+
const value = goStringLiteralValue(argsNode.namedChild(i));
|
|
11267
|
+
if (value !== null) return value;
|
|
11268
|
+
}
|
|
11269
|
+
return null;
|
|
11270
|
+
}
|
|
11157
11271
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11158
11272
|
if (import_node_path47.default.extname(file.path) !== ".go") return [];
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
const
|
|
11162
|
-
|
|
11163
|
-
);
|
|
11273
|
+
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11274
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11275
|
+
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
11276
|
+
const importsSqlx = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([SQLX_IMPORT]));
|
|
11277
|
+
if (!importsDatabaseSql && !importsSqlx) return [];
|
|
11164
11278
|
const out = [];
|
|
11165
11279
|
walk7(tree.rootNode, (node) => {
|
|
11166
11280
|
if (node.type !== "call_expression") return;
|
|
11167
11281
|
const fn = node.childForFieldName("function");
|
|
11168
11282
|
if (fn?.type !== "selector_expression") return;
|
|
11169
11283
|
const method = fn.childForFieldName("field")?.text;
|
|
11170
|
-
if (!method
|
|
11171
|
-
const
|
|
11172
|
-
if (!
|
|
11173
|
-
const sql =
|
|
11284
|
+
if (!method) return;
|
|
11285
|
+
const recognized = DATABASE_SQL_METHODS.has(method) || importsSqlx && SQLX_METHODS.has(method);
|
|
11286
|
+
if (!recognized) return;
|
|
11287
|
+
const sql = firstStringLiteralArg(node.childForFieldName("arguments"));
|
|
11288
|
+
if (sql === null) return;
|
|
11174
11289
|
const table = tableFromSqlStatement(sql);
|
|
11175
11290
|
if (!table) return;
|
|
11291
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11176
11292
|
const line = node.startPosition.row + 1;
|
|
11177
11293
|
out.push({
|
|
11178
11294
|
infraId: (0, import_types35.infraId)("sql-table", table),
|
|
@@ -11180,7 +11296,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11180
11296
|
kind: "sql-table",
|
|
11181
11297
|
edgeType: "CALLS",
|
|
11182
11298
|
confidenceKind: "verified-call-site",
|
|
11183
|
-
|
|
11299
|
+
...columns.length > 0 ? { columns } : {},
|
|
11300
|
+
evidence: {
|
|
11301
|
+
file: toPosix(import_node_path47.default.relative(serviceDir, file.path)),
|
|
11302
|
+
line,
|
|
11303
|
+
snippet: snippet(file.content, line)
|
|
11304
|
+
}
|
|
11184
11305
|
});
|
|
11185
11306
|
});
|
|
11186
11307
|
return out;
|
|
@@ -11194,12 +11315,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11194
11315
|
var import_types36 = require("@neat.is/types");
|
|
11195
11316
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11196
11317
|
var PARSE_CHUNK11 = 16384;
|
|
11197
|
-
function
|
|
11318
|
+
function makeGoParser4() {
|
|
11198
11319
|
const p = new import_tree_sitter15.default();
|
|
11199
11320
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11200
11321
|
return p;
|
|
11201
11322
|
}
|
|
11202
|
-
function
|
|
11323
|
+
function parseSource11(parser, source) {
|
|
11203
11324
|
return parser.parse(
|
|
11204
11325
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11205
11326
|
);
|
|
@@ -11623,7 +11744,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11623
11744
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11624
11745
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11625
11746
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11626
|
-
const tree =
|
|
11747
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11627
11748
|
const { structs, models, tableFor } = analyze(tree);
|
|
11628
11749
|
const out = [];
|
|
11629
11750
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -11654,7 +11775,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11654
11775
|
function gormForeignKeys(file, serviceDir) {
|
|
11655
11776
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11656
11777
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11657
|
-
const tree =
|
|
11778
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11658
11779
|
const { structs, models, tableFor } = analyze(tree);
|
|
11659
11780
|
const out = [];
|
|
11660
11781
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12784,7 +12905,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
|
|
|
12784
12905
|
var import_types47 = require("@neat.is/types");
|
|
12785
12906
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
12786
12907
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
12787
|
-
function
|
|
12908
|
+
function parserForExt4(ext) {
|
|
12788
12909
|
const p = new import_tree_sitter16.default();
|
|
12789
12910
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
|
|
12790
12911
|
return p;
|
|
@@ -12873,7 +12994,7 @@ function topLevelSchemas(root) {
|
|
|
12873
12994
|
}
|
|
12874
12995
|
function zodShapesFromFile(file, serviceDir) {
|
|
12875
12996
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
12876
|
-
const tree = parseSource3(
|
|
12997
|
+
const tree = parseSource3(parserForExt4(import_node_path57.default.extname(file.path)), file.content);
|
|
12877
12998
|
const out = [];
|
|
12878
12999
|
const seen = /* @__PURE__ */ new Set();
|
|
12879
13000
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13449,7 +13570,7 @@ var Projects = class {
|
|
|
13449
13570
|
init_cjs_shims();
|
|
13450
13571
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
13451
13572
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
13452
|
-
var
|
|
13573
|
+
var import_types85 = require("@neat.is/types");
|
|
13453
13574
|
|
|
13454
13575
|
// src/extend/index.ts
|
|
13455
13576
|
init_cjs_shims();
|
|
@@ -14845,6 +14966,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
14845
14966
|
unresolved++;
|
|
14846
14967
|
continue;
|
|
14847
14968
|
}
|
|
14969
|
+
if (signal.incident) {
|
|
14970
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
14971
|
+
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
14972
|
+
unresolved++;
|
|
14973
|
+
continue;
|
|
14974
|
+
}
|
|
14975
|
+
await appendConnectorIncident(ctx.errorsPath, {
|
|
14976
|
+
id: signal.incident.id,
|
|
14977
|
+
timestamp: signal.incident.timestamp,
|
|
14978
|
+
service: signal.incident.service,
|
|
14979
|
+
errorType: signal.incident.errorType,
|
|
14980
|
+
errorMessage: signal.incident.errorMessage,
|
|
14981
|
+
...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
|
|
14982
|
+
affectedNode: resolved.targetNodeId
|
|
14983
|
+
});
|
|
14984
|
+
continue;
|
|
14985
|
+
}
|
|
14848
14986
|
if (resolved.ensureInfraNode) {
|
|
14849
14987
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
14850
14988
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -17079,6 +17217,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
17079
17217
|
};
|
|
17080
17218
|
}
|
|
17081
17219
|
|
|
17220
|
+
// src/connectors/eas/index.ts
|
|
17221
|
+
init_cjs_shims();
|
|
17222
|
+
|
|
17223
|
+
// src/connectors/eas/client.ts
|
|
17224
|
+
init_cjs_shims();
|
|
17225
|
+
|
|
17226
|
+
// src/connectors/eas/types.ts
|
|
17227
|
+
init_cjs_shims();
|
|
17228
|
+
function readEasCredentials(raw) {
|
|
17229
|
+
const token = raw["token"];
|
|
17230
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
17231
|
+
throw new Error("eas connector: credentials.token (EXPO_TOKEN) must be a non-empty string");
|
|
17232
|
+
}
|
|
17233
|
+
return { token };
|
|
17234
|
+
}
|
|
17235
|
+
var EAS_STATUS_ERRORED = "ERRORED";
|
|
17236
|
+
var TRANSIENT_BUILD_PHASES = /* @__PURE__ */ new Set([
|
|
17237
|
+
"SPIN_UP_BUILDER",
|
|
17238
|
+
"PREPARE_CREDENTIALS",
|
|
17239
|
+
"RESTORE_CACHE",
|
|
17240
|
+
"UPLOAD_APPLICATION_ARCHIVE"
|
|
17241
|
+
]);
|
|
17242
|
+
var INTERNAL_ERROR_CODE = /INTERNAL_SERVER_ERROR|EAS_BUILD_.*INTERNAL|_INTERNAL_ERROR|UNKNOWN_ERROR/i;
|
|
17243
|
+
function isTransientFailure(err) {
|
|
17244
|
+
if (!err) return false;
|
|
17245
|
+
const phase = typeof err.buildPhase === "string" ? err.buildPhase : void 0;
|
|
17246
|
+
if (phase) {
|
|
17247
|
+
if (TRANSIENT_BUILD_PHASES.has(phase)) return true;
|
|
17248
|
+
if (/^SPIN_UP|_CREDENTIALS$|^RESTORE_CACHE/i.test(phase)) return true;
|
|
17249
|
+
}
|
|
17250
|
+
const code = typeof err.errorCode === "string" ? err.errorCode : void 0;
|
|
17251
|
+
if (code && INTERNAL_ERROR_CODE.test(code)) return true;
|
|
17252
|
+
return false;
|
|
17253
|
+
}
|
|
17254
|
+
var FIELD_SEP3 = "\0";
|
|
17255
|
+
var EAS_TARGET_KIND = "eas-build";
|
|
17256
|
+
function packEasTargetName(identity) {
|
|
17257
|
+
return [identity.serviceName, identity.phase].join(FIELD_SEP3);
|
|
17258
|
+
}
|
|
17259
|
+
function parseEasTargetName(targetName) {
|
|
17260
|
+
const sep = targetName.indexOf(FIELD_SEP3);
|
|
17261
|
+
if (sep === -1) return null;
|
|
17262
|
+
const serviceName = targetName.slice(0, sep);
|
|
17263
|
+
const phase = targetName.slice(sep + 1);
|
|
17264
|
+
if (!serviceName) return null;
|
|
17265
|
+
return { serviceName, phase };
|
|
17266
|
+
}
|
|
17267
|
+
|
|
17268
|
+
// src/connectors/eas/client.ts
|
|
17269
|
+
var DEFAULT_EAS_API_URL = "https://api.expo.dev/graphql";
|
|
17270
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
17271
|
+
var DEFAULT_MAX_PAGES = 10;
|
|
17272
|
+
var DEFAULT_MAX_LOG_BYTES = 16 * 1024;
|
|
17273
|
+
var DEFAULT_MAX_LOOKBACK_MS6 = 7 * 24 * 60 * 60 * 1e3;
|
|
17274
|
+
var BUILDS_QUERY = `
|
|
17275
|
+
query NeatEasErroredBuilds($appId: String!, $offset: Int!, $limit: Int!) {
|
|
17276
|
+
app {
|
|
17277
|
+
byId(appId: $appId) {
|
|
17278
|
+
builds(offset: $offset, limit: $limit, filter: { status: ERRORED }) {
|
|
17279
|
+
id
|
|
17280
|
+
status
|
|
17281
|
+
platform
|
|
17282
|
+
buildProfile
|
|
17283
|
+
gitCommitHash
|
|
17284
|
+
gitCommitMessage
|
|
17285
|
+
gitRef
|
|
17286
|
+
isGitWorkingTreeDirty
|
|
17287
|
+
createdAt
|
|
17288
|
+
completedAt
|
|
17289
|
+
error {
|
|
17290
|
+
buildPhase
|
|
17291
|
+
errorCode
|
|
17292
|
+
message
|
|
17293
|
+
docsUrl
|
|
17294
|
+
}
|
|
17295
|
+
logFileUrls
|
|
17296
|
+
}
|
|
17297
|
+
}
|
|
17298
|
+
}
|
|
17299
|
+
}
|
|
17300
|
+
`;
|
|
17301
|
+
async function easGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
17302
|
+
const res = await junctionFetch(
|
|
17303
|
+
apiUrl,
|
|
17304
|
+
{
|
|
17305
|
+
method: "POST",
|
|
17306
|
+
headers: {
|
|
17307
|
+
"Content-Type": "application/json",
|
|
17308
|
+
...bearerAuthHeader(token)
|
|
17309
|
+
},
|
|
17310
|
+
body: JSON.stringify({ query, variables })
|
|
17311
|
+
},
|
|
17312
|
+
// accountKey: the Expo app id — the per-(provider, accountKey) rate-limit
|
|
17313
|
+
// bucket (ADR-131), the closest thing this connector carries to "one account".
|
|
17314
|
+
{ provider: "eas", accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
17315
|
+
);
|
|
17316
|
+
if (!res.ok) {
|
|
17317
|
+
throw new Error(`Expo GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
17318
|
+
}
|
|
17319
|
+
const body = await res.json();
|
|
17320
|
+
if (body.errors && body.errors.length > 0) {
|
|
17321
|
+
throw new Error(`Expo GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
|
|
17322
|
+
}
|
|
17323
|
+
if (!body.data) throw new Error("Expo GraphQL response carried no data");
|
|
17324
|
+
return body.data;
|
|
17325
|
+
}
|
|
17326
|
+
async function fetchErroredBuilds(token, config, fetchImpl) {
|
|
17327
|
+
const apiUrl = config.apiUrl ?? DEFAULT_EAS_API_URL;
|
|
17328
|
+
const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
17329
|
+
const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;
|
|
17330
|
+
const out = [];
|
|
17331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17332
|
+
for (let page = 0; page < maxPages; page++) {
|
|
17333
|
+
const data = await easGraphQL(
|
|
17334
|
+
apiUrl,
|
|
17335
|
+
token,
|
|
17336
|
+
BUILDS_QUERY,
|
|
17337
|
+
{ appId: config.appId, offset: page * pageSize, limit: pageSize },
|
|
17338
|
+
config.appId,
|
|
17339
|
+
fetchImpl
|
|
17340
|
+
);
|
|
17341
|
+
const builds = data.app?.byId?.builds;
|
|
17342
|
+
if (!Array.isArray(builds)) break;
|
|
17343
|
+
let added = 0;
|
|
17344
|
+
for (const b of builds) {
|
|
17345
|
+
if (!b || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
17346
|
+
if (b.status !== EAS_STATUS_ERRORED) continue;
|
|
17347
|
+
if (seen.has(b.id)) continue;
|
|
17348
|
+
seen.add(b.id);
|
|
17349
|
+
out.push(b);
|
|
17350
|
+
added++;
|
|
17351
|
+
}
|
|
17352
|
+
if (builds.length < pageSize) break;
|
|
17353
|
+
if (added === 0) break;
|
|
17354
|
+
}
|
|
17355
|
+
return out;
|
|
17356
|
+
}
|
|
17357
|
+
async function fetchBuildLogs(logFileUrls, maxBytes = DEFAULT_MAX_LOG_BYTES, fetchImpl) {
|
|
17358
|
+
if (!Array.isArray(logFileUrls) || logFileUrls.length === 0) return "";
|
|
17359
|
+
const doFetch = fetchImpl ?? fetch;
|
|
17360
|
+
const chunks = [];
|
|
17361
|
+
for (const url of logFileUrls) {
|
|
17362
|
+
if (typeof url !== "string" || url.length === 0) continue;
|
|
17363
|
+
try {
|
|
17364
|
+
const res = await doFetch(url);
|
|
17365
|
+
if (!res.ok) continue;
|
|
17366
|
+
chunks.push(await res.text());
|
|
17367
|
+
} catch {
|
|
17368
|
+
}
|
|
17369
|
+
}
|
|
17370
|
+
const joined = chunks.join("\n");
|
|
17371
|
+
return joined.length > maxBytes ? joined.slice(joined.length - maxBytes) : joined;
|
|
17372
|
+
}
|
|
17373
|
+
|
|
17374
|
+
// src/connectors/eas/map.ts
|
|
17375
|
+
init_cjs_shims();
|
|
17376
|
+
function buildEventTime(build) {
|
|
17377
|
+
if (typeof build.completedAt === "string" && build.completedAt.length > 0) return build.completedAt;
|
|
17378
|
+
if (typeof build.createdAt === "string" && build.createdAt.length > 0) return build.createdAt;
|
|
17379
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17380
|
+
}
|
|
17381
|
+
function incidentMessage2(build) {
|
|
17382
|
+
const err = build.error ?? {};
|
|
17383
|
+
const phase = typeof err.buildPhase === "string" && err.buildPhase.length > 0 ? ` at ${err.buildPhase}` : "";
|
|
17384
|
+
const detail = typeof err.message === "string" && err.message.trim().length > 0 && err.message.trim() || typeof err.errorCode === "string" && err.errorCode.length > 0 && err.errorCode || "no error detail reported";
|
|
17385
|
+
let msg = `EAS build failed${phase}: ${detail}`;
|
|
17386
|
+
if (build.isGitWorkingTreeDirty === true) {
|
|
17387
|
+
msg += " (built from a dirty working tree \u2014 the commit may not represent what built)";
|
|
17388
|
+
}
|
|
17389
|
+
return msg;
|
|
17390
|
+
}
|
|
17391
|
+
function incidentAttributes(build) {
|
|
17392
|
+
const attrs = {};
|
|
17393
|
+
const err = build.error ?? {};
|
|
17394
|
+
const put = (k, v) => {
|
|
17395
|
+
if (typeof v === "string" && v.length === 0) return;
|
|
17396
|
+
if (v !== void 0 && v !== null) attrs[k] = v;
|
|
17397
|
+
};
|
|
17398
|
+
put("eas.buildId", build.id);
|
|
17399
|
+
put("eas.platform", build.platform ?? void 0);
|
|
17400
|
+
put("eas.buildProfile", build.buildProfile ?? void 0);
|
|
17401
|
+
put("eas.buildPhase", err.buildPhase ?? void 0);
|
|
17402
|
+
put("eas.errorCode", err.errorCode ?? void 0);
|
|
17403
|
+
put("eas.docsUrl", err.docsUrl ?? void 0);
|
|
17404
|
+
put("eas.gitCommitHash", build.gitCommitHash ?? void 0);
|
|
17405
|
+
put("eas.gitRef", build.gitRef ?? void 0);
|
|
17406
|
+
put("eas.gitCommitMessage", build.gitCommitMessage ?? void 0);
|
|
17407
|
+
if (typeof build.isGitWorkingTreeDirty === "boolean") {
|
|
17408
|
+
attrs["eas.gitWorkingTreeDirty"] = build.isGitWorkingTreeDirty;
|
|
17409
|
+
if (build.isGitWorkingTreeDirty) attrs["eas.confidence"] = "low";
|
|
17410
|
+
}
|
|
17411
|
+
put("eas.createdAt", build.createdAt ?? void 0);
|
|
17412
|
+
put("eas.completedAt", build.completedAt ?? void 0);
|
|
17413
|
+
if (typeof build.logsText === "string" && build.logsText.length > 0) {
|
|
17414
|
+
attrs["eas.logs"] = build.logsText;
|
|
17415
|
+
}
|
|
17416
|
+
return attrs;
|
|
17417
|
+
}
|
|
17418
|
+
function mapBuildToSignal(build, serviceName) {
|
|
17419
|
+
if (!build || typeof build !== "object") return null;
|
|
17420
|
+
if (build.status !== EAS_STATUS_ERRORED) return null;
|
|
17421
|
+
if (!build.error) return null;
|
|
17422
|
+
if (isTransientFailure(build.error)) return null;
|
|
17423
|
+
const timestamp = buildEventTime(build);
|
|
17424
|
+
const phase = typeof build.error.buildPhase === "string" ? build.error.buildPhase : "";
|
|
17425
|
+
return {
|
|
17426
|
+
targetKind: EAS_TARGET_KIND,
|
|
17427
|
+
targetName: packEasTargetName({ serviceName, phase }),
|
|
17428
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
17429
|
+
callCount: 0,
|
|
17430
|
+
errorCount: 0,
|
|
17431
|
+
lastObservedIso: timestamp,
|
|
17432
|
+
incident: {
|
|
17433
|
+
id: `eas:build:${build.id}`,
|
|
17434
|
+
timestamp,
|
|
17435
|
+
service: serviceName,
|
|
17436
|
+
errorType: "eas-build-failure",
|
|
17437
|
+
errorMessage: incidentMessage2(build),
|
|
17438
|
+
attributes: incidentAttributes(build)
|
|
17439
|
+
}
|
|
17440
|
+
};
|
|
17441
|
+
}
|
|
17442
|
+
function mapBuildsToSignals(builds, serviceName) {
|
|
17443
|
+
const out = [];
|
|
17444
|
+
for (const build of builds) {
|
|
17445
|
+
const signal = mapBuildToSignal(build, serviceName);
|
|
17446
|
+
if (signal) out.push(signal);
|
|
17447
|
+
}
|
|
17448
|
+
return out;
|
|
17449
|
+
}
|
|
17450
|
+
|
|
17451
|
+
// src/connectors/eas/resolve.ts
|
|
17452
|
+
init_cjs_shims();
|
|
17453
|
+
var import_types82 = require("@neat.is/types");
|
|
17454
|
+
var NO_ENV2 = "unknown";
|
|
17455
|
+
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17456
|
+
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
17457
|
+
"READ_APP_CONFIG",
|
|
17458
|
+
"CONFIGURE_EXPO_UPDATES",
|
|
17459
|
+
"CALCULATE_EXPO_UPDATES_RUNTIME_VERSION"
|
|
17460
|
+
]);
|
|
17461
|
+
function configBasenamesForPhase(phase) {
|
|
17462
|
+
if (EAS_JSON_PHASES.has(phase)) return ["eas.json"];
|
|
17463
|
+
if (APP_CONFIG_PHASES.has(phase)) return ["app.json", "app.config.json"];
|
|
17464
|
+
return [];
|
|
17465
|
+
}
|
|
17466
|
+
function configNodeService(graph, configNodeId) {
|
|
17467
|
+
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
17468
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
17469
|
+
if (edge.type !== import_types82.EdgeType.CONFIGURED_BY) continue;
|
|
17470
|
+
const parsed = (0, import_types82.parseFileId)(edge.source);
|
|
17471
|
+
if (parsed) return parsed.service;
|
|
17472
|
+
}
|
|
17473
|
+
return null;
|
|
17474
|
+
}
|
|
17475
|
+
function findConfigNode(graph, basenames, serviceName) {
|
|
17476
|
+
let scoped = null;
|
|
17477
|
+
let anyMatch = null;
|
|
17478
|
+
graph.forEachNode((id, attrs) => {
|
|
17479
|
+
if (scoped) return;
|
|
17480
|
+
const node = attrs;
|
|
17481
|
+
if (node.type !== import_types82.NodeType.ConfigNode) return;
|
|
17482
|
+
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17483
|
+
if (anyMatch === null) anyMatch = id;
|
|
17484
|
+
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
17485
|
+
});
|
|
17486
|
+
return scoped ?? anyMatch;
|
|
17487
|
+
}
|
|
17488
|
+
function createEasResolveTarget(graph) {
|
|
17489
|
+
return (signal) => {
|
|
17490
|
+
if (signal.targetKind !== EAS_TARGET_KIND) return null;
|
|
17491
|
+
const identity = parseEasTargetName(signal.targetName);
|
|
17492
|
+
if (!identity) return null;
|
|
17493
|
+
const { serviceName, phase } = identity;
|
|
17494
|
+
const basenames = configBasenamesForPhase(phase);
|
|
17495
|
+
if (basenames.length > 0) {
|
|
17496
|
+
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
17497
|
+
if (configNodeId) {
|
|
17498
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types82.EdgeType.CALLS };
|
|
17499
|
+
}
|
|
17500
|
+
}
|
|
17501
|
+
return {
|
|
17502
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
17503
|
+
serviceName,
|
|
17504
|
+
edgeType: import_types82.EdgeType.CALLS
|
|
17505
|
+
};
|
|
17506
|
+
};
|
|
17507
|
+
}
|
|
17508
|
+
|
|
17509
|
+
// src/connectors/eas/index.ts
|
|
17510
|
+
function isBuildSince(build, sinceIso) {
|
|
17511
|
+
const t = Date.parse(buildEventTime(build));
|
|
17512
|
+
const s = Date.parse(sinceIso);
|
|
17513
|
+
if (Number.isNaN(t) || Number.isNaN(s)) return true;
|
|
17514
|
+
return t > s;
|
|
17515
|
+
}
|
|
17516
|
+
function boundedSinceIso2(since, now, maxLookbackMs) {
|
|
17517
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
17518
|
+
if (!since) return floor.toISOString();
|
|
17519
|
+
const sinceMs = new Date(since).getTime();
|
|
17520
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
17521
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
17522
|
+
}
|
|
17523
|
+
var EasConnector = class {
|
|
17524
|
+
constructor(config, fetchImpl) {
|
|
17525
|
+
this.config = config;
|
|
17526
|
+
this.fetchImpl = fetchImpl;
|
|
17527
|
+
}
|
|
17528
|
+
config;
|
|
17529
|
+
fetchImpl;
|
|
17530
|
+
provider = "eas";
|
|
17531
|
+
async poll(ctx) {
|
|
17532
|
+
const creds = readEasCredentials(ctx.credentials);
|
|
17533
|
+
const serviceName = this.config.serviceName ?? this.config.appId;
|
|
17534
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS6;
|
|
17535
|
+
const sinceIso = boundedSinceIso2(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
17536
|
+
const builds = await fetchErroredBuilds(creds.token, this.config, this.fetchImpl);
|
|
17537
|
+
const fresh = builds.filter((b) => isBuildSince(b, sinceIso));
|
|
17538
|
+
const maxLogBytes = this.config.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
17539
|
+
for (const build of fresh) {
|
|
17540
|
+
build.logsText = await fetchBuildLogs(build.logFileUrls, maxLogBytes, this.fetchImpl);
|
|
17541
|
+
}
|
|
17542
|
+
return mapBuildsToSignals(fresh, serviceName);
|
|
17543
|
+
}
|
|
17544
|
+
};
|
|
17545
|
+
function createEasConnector(graph, config, fetchImpl) {
|
|
17546
|
+
return {
|
|
17547
|
+
connector: new EasConnector(config, fetchImpl),
|
|
17548
|
+
resolveTarget: createEasResolveTarget(graph)
|
|
17549
|
+
};
|
|
17550
|
+
}
|
|
17551
|
+
|
|
17082
17552
|
// src/connectors/registry.ts
|
|
17083
17553
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
17084
17554
|
async function authProbe(input) {
|
|
@@ -17366,6 +17836,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17366
17836
|
...fetchImpl ? { fetchImpl } : {}
|
|
17367
17837
|
});
|
|
17368
17838
|
}
|
|
17839
|
+
},
|
|
17840
|
+
eas: {
|
|
17841
|
+
provider: "eas",
|
|
17842
|
+
// The secret is a single robot-user EXPO_TOKEN; a single-string credential
|
|
17843
|
+
// maps to `token`. `appId` is non-secret config (connector-config.md §7.1).
|
|
17844
|
+
primaryCredentialKey: "token",
|
|
17845
|
+
requiredCredentialFields: ["token"],
|
|
17846
|
+
requiredOptionFields: ["appId"],
|
|
17847
|
+
build(graph, options) {
|
|
17848
|
+
return createEasConnector(graph, options);
|
|
17849
|
+
},
|
|
17850
|
+
// Runs the connector's real `builds` query at limit 1 — the exact read poll()
|
|
17851
|
+
// performs, minus the pages — so the probe checks both that the EXPO_TOKEN
|
|
17852
|
+
// authenticates and that this app id is reachable, the same probe-the-real-
|
|
17853
|
+
// query discipline Railway and Cloud Run use over a trivial `{ __typename }`.
|
|
17854
|
+
// A bad or wrong-scoped token comes back as an Expo GraphQL error, which
|
|
17855
|
+
// `fetchErroredBuilds` throws on, so it fails honestly here rather than
|
|
17856
|
+
// silently at the first poll.
|
|
17857
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
17858
|
+
const cfg = options;
|
|
17859
|
+
const appId = String(cfg.appId ?? "");
|
|
17860
|
+
if (!appId) return { ok: false, reason: "eas: appId is required to validate" };
|
|
17861
|
+
const probeConfig = {
|
|
17862
|
+
appId,
|
|
17863
|
+
pageSize: 1,
|
|
17864
|
+
maxPages: 1,
|
|
17865
|
+
...cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}
|
|
17866
|
+
};
|
|
17867
|
+
try {
|
|
17868
|
+
await fetchErroredBuilds(String(credentials.token ?? ""), probeConfig, fetchImpl);
|
|
17869
|
+
return { ok: true };
|
|
17870
|
+
} catch (err) {
|
|
17871
|
+
return { ok: false, reason: `eas auth check failed: ${err.message}` };
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17369
17874
|
}
|
|
17370
17875
|
};
|
|
17371
17876
|
function vercelCredsFrom(credentials) {
|
|
@@ -17547,7 +18052,11 @@ async function startConnectorPolling(input) {
|
|
|
17547
18052
|
const stopFns = all.map(
|
|
17548
18053
|
(registration) => startConnectorPollLoop(
|
|
17549
18054
|
registration.connector,
|
|
17550
|
-
{
|
|
18055
|
+
{
|
|
18056
|
+
projectDir: input.projectDir,
|
|
18057
|
+
credentials: registration.credentials,
|
|
18058
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
18059
|
+
},
|
|
17551
18060
|
input.graph,
|
|
17552
18061
|
registration.resolveTarget,
|
|
17553
18062
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -17717,11 +18226,11 @@ function registerRoutes(scope, ctx) {
|
|
|
17717
18226
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17718
18227
|
const parsed = [];
|
|
17719
18228
|
for (const c of candidates) {
|
|
17720
|
-
const r =
|
|
18229
|
+
const r = import_types85.DivergenceTypeSchema.safeParse(c);
|
|
17721
18230
|
if (!r.success) {
|
|
17722
18231
|
return reply.code(400).send({
|
|
17723
18232
|
error: `unknown divergence type "${c}"`,
|
|
17724
|
-
allowed:
|
|
18233
|
+
allowed: import_types85.DivergenceTypeSchema.options
|
|
17725
18234
|
});
|
|
17726
18235
|
}
|
|
17727
18236
|
parsed.push(r.data);
|
|
@@ -17828,10 +18337,15 @@ function registerRoutes(scope, ctx) {
|
|
|
17828
18337
|
}
|
|
17829
18338
|
const reg = built.registration;
|
|
17830
18339
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18340
|
+
const incidentsPath = errorsPathFor(proj);
|
|
17831
18341
|
try {
|
|
17832
18342
|
const result = await ctx.runPoll(
|
|
17833
18343
|
reg.connector,
|
|
17834
|
-
{
|
|
18344
|
+
{
|
|
18345
|
+
projectDir: proj.scanPath ?? "",
|
|
18346
|
+
credentials: reg.credentials,
|
|
18347
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
18348
|
+
},
|
|
17835
18349
|
proj.graph,
|
|
17836
18350
|
reg.resolveTarget
|
|
17837
18351
|
);
|
|
@@ -18030,7 +18544,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18030
18544
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
18031
18545
|
let violations = await log.readAll();
|
|
18032
18546
|
if (req2.query.severity) {
|
|
18033
|
-
const sev =
|
|
18547
|
+
const sev = import_types85.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
18034
18548
|
if (!sev.success) {
|
|
18035
18549
|
return reply.code(400).send({
|
|
18036
18550
|
error: "invalid severity",
|
|
@@ -18069,7 +18583,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18069
18583
|
scope.post("/policies/check", async (req2, reply) => {
|
|
18070
18584
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
18071
18585
|
if (!proj) return;
|
|
18072
|
-
const parsed =
|
|
18586
|
+
const parsed = import_types85.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
18073
18587
|
if (!parsed.success) {
|
|
18074
18588
|
return reply.code(400).send({
|
|
18075
18589
|
error: "invalid /policies/check body",
|
|
@@ -18410,7 +18924,7 @@ function unroutedErrorsPath(neatHome4) {
|
|
|
18410
18924
|
}
|
|
18411
18925
|
|
|
18412
18926
|
// src/daemon.ts
|
|
18413
|
-
var
|
|
18927
|
+
var import_types86 = require("@neat.is/types");
|
|
18414
18928
|
function daemonJsonPath(scanPath) {
|
|
18415
18929
|
return import_node_path67.default.join(scanPath, "neat-out", "daemon.json");
|
|
18416
18930
|
}
|
|
@@ -18549,7 +19063,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
18549
19063
|
if (!serviceName) return true;
|
|
18550
19064
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
18551
19065
|
return graph.someNode(
|
|
18552
|
-
(_id, attrs) => attrs.type ===
|
|
19066
|
+
(_id, attrs) => attrs.type === import_types86.NodeType.ServiceNode && attrs.name === serviceName
|
|
18553
19067
|
);
|
|
18554
19068
|
}
|
|
18555
19069
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
@@ -18597,6 +19111,10 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
|
18597
19111
|
project: entry2.name,
|
|
18598
19112
|
graph,
|
|
18599
19113
|
projectDir: entry2.path,
|
|
19114
|
+
// The slot's incident ledger, so an incident-emitting connector (ADR-185)
|
|
19115
|
+
// writes a build-failure incident onto the same errors.ndjson OTLP-derived
|
|
19116
|
+
// incidents land in.
|
|
19117
|
+
errorsPath: paths.errorsPath,
|
|
18600
19118
|
...neatHome4 ? { home: neatHome4 } : {},
|
|
18601
19119
|
extra: connectors,
|
|
18602
19120
|
onSkip: (skipped, reason) => console.warn(
|
|
@@ -18839,6 +19357,19 @@ async function startDaemon(opts = {}) {
|
|
|
18839
19357
|
let otlpAddress = "";
|
|
18840
19358
|
let daemonRecord = null;
|
|
18841
19359
|
if (bind) {
|
|
19360
|
+
let bareSpanIsRoutable2 = function(serviceName) {
|
|
19361
|
+
if (singleProject) {
|
|
19362
|
+
const slot = slots.get(singleProject);
|
|
19363
|
+
if (!slot) {
|
|
19364
|
+
return !serviceName || serviceNameMatchesProject(serviceName, singleProject);
|
|
19365
|
+
}
|
|
19366
|
+
return spanBelongsToSingleProject(slot.graph, singleProject, serviceName);
|
|
19367
|
+
}
|
|
19368
|
+
const entries = [...slots.values()].map((s) => s.entry);
|
|
19369
|
+
const target = routeSpanToProject(serviceName, entries);
|
|
19370
|
+
return slots.has(target) || slots.has(DEFAULT_PROJECT);
|
|
19371
|
+
};
|
|
19372
|
+
var bareSpanIsRoutable = bareSpanIsRoutable2;
|
|
18842
19373
|
const auth = readAuthEnv();
|
|
18843
19374
|
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
18844
19375
|
const restPort = resolveRestPort(opts);
|
|
@@ -18936,6 +19467,20 @@ async function startDaemon(opts = {}) {
|
|
|
18936
19467
|
const liveEntries = await listProjects().catch(() => []);
|
|
18937
19468
|
let slot = slots.get(project);
|
|
18938
19469
|
if (!slot) {
|
|
19470
|
+
if (singleProject && project === singleProject) {
|
|
19471
|
+
slot = await tryRecoverSlot({
|
|
19472
|
+
name: singleProject,
|
|
19473
|
+
path: singleProjectPath,
|
|
19474
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19475
|
+
languages: [],
|
|
19476
|
+
status: "active"
|
|
19477
|
+
});
|
|
19478
|
+
if (!slot || slot.status !== "active") {
|
|
19479
|
+
warnDroppedSpan(singleProject, slot?.errorReason ?? "unknown");
|
|
19480
|
+
return null;
|
|
19481
|
+
}
|
|
19482
|
+
return slot;
|
|
19483
|
+
}
|
|
18939
19484
|
await recordUnroutedSpan(serviceName, traceId);
|
|
18940
19485
|
return null;
|
|
18941
19486
|
}
|
|
@@ -19002,7 +19547,30 @@ async function startDaemon(opts = {}) {
|
|
|
19002
19547
|
// host, rather than accepting it and dropping the batch. `slots` covers
|
|
19003
19548
|
// active/recovering projects, `bootstrapStatus` the ones still
|
|
19004
19549
|
// extracting; a foreign or wrong-cased project name matches neither.
|
|
19005
|
-
|
|
19550
|
+
// A single-project daemon owns exactly one project by definition, so its
|
|
19551
|
+
// own name always counts as registered even before loadAll populates the
|
|
19552
|
+
// slot — otherwise a scoped span arriving during cold-start would 404 and
|
|
19553
|
+
// be lost (OTLP does not retry 4xx). resolveSlotByName then builds it (#879).
|
|
19554
|
+
isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project) || singleProject !== void 0 && project === singleProject,
|
|
19555
|
+
// #881 — the bare `/v1/traces` route replies before the span is routed
|
|
19556
|
+
// (off the queue), so tell the receiver, per batch, how many spans will
|
|
19557
|
+
// land on no project. It keeps the 200 but reports those as
|
|
19558
|
+
// partialSuccess.rejectedSpans instead of an empty partialSuccess that an
|
|
19559
|
+
// exporter reads as full acceptance. Pure — the drop + unrouted-ledger
|
|
19560
|
+
// write still happen on the async onSpan path.
|
|
19561
|
+
classifyBareRoutability: (spans) => {
|
|
19562
|
+
let rejected = 0;
|
|
19563
|
+
for (const span of spans) {
|
|
19564
|
+
if (!bareSpanIsRoutable2(span.service)) rejected++;
|
|
19565
|
+
}
|
|
19566
|
+
if (rejected === 0) return { rejected: 0 };
|
|
19567
|
+
const noun = rejected === 1 ? "span" : "spans";
|
|
19568
|
+
const verb = rejected === 1 ? "was" : "were";
|
|
19569
|
+
return {
|
|
19570
|
+
rejected,
|
|
19571
|
+
message: `${rejected} ${noun} matched no project on this daemon and ${verb} dropped. Export to /projects/<project>/v1/traces, or check the exporter's service.name.`
|
|
19572
|
+
};
|
|
19573
|
+
}
|
|
19006
19574
|
});
|
|
19007
19575
|
otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
|
|
19008
19576
|
console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`);
|