@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/index.cjs
CHANGED
|
@@ -503,13 +503,18 @@ function loadProtobufResponseEncoder() {
|
|
|
503
503
|
);
|
|
504
504
|
return exportTraceServiceResponseType;
|
|
505
505
|
}
|
|
506
|
-
function encodeProtobufResponseBody() {
|
|
507
|
-
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
506
|
+
function encodeProtobufResponseBody(rejected, message) {
|
|
508
507
|
const Type = loadProtobufResponseEncoder();
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
508
|
+
if (!rejected) {
|
|
509
|
+
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
510
|
+
const msg2 = Type.create({});
|
|
511
|
+
cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
|
|
512
|
+
return cachedProtobufResponseBody;
|
|
513
|
+
}
|
|
514
|
+
const msg = Type.fromObject({
|
|
515
|
+
partial_success: { rejected_spans: rejected, error_message: message ?? "" }
|
|
516
|
+
});
|
|
517
|
+
return Buffer.from(Type.encode(msg).finish());
|
|
513
518
|
}
|
|
514
519
|
async function decodeProtobufBody(buf) {
|
|
515
520
|
const Type = loadProtobufDecoder();
|
|
@@ -624,6 +629,13 @@ async function buildOtelReceiver(opts) {
|
|
|
624
629
|
}
|
|
625
630
|
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
626
631
|
}
|
|
632
|
+
function sendOtlpPartial(reply, flavor, rejected, message) {
|
|
633
|
+
if (flavor === "protobuf") {
|
|
634
|
+
const buf = encodeProtobufResponseBody(rejected, message);
|
|
635
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
636
|
+
}
|
|
637
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
|
|
638
|
+
}
|
|
627
639
|
app.addContentTypeParser(
|
|
628
640
|
"application/x-protobuf",
|
|
629
641
|
{ parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
|
|
@@ -651,6 +663,10 @@ async function buildOtelReceiver(opts) {
|
|
|
651
663
|
}
|
|
652
664
|
}
|
|
653
665
|
enqueue(spans);
|
|
666
|
+
if (opts.classifyBareRoutability) {
|
|
667
|
+
const { rejected, message } = opts.classifyBareRoutability(spans);
|
|
668
|
+
if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
|
|
669
|
+
}
|
|
654
670
|
return sendOtlpSuccess(reply, result.flavor);
|
|
655
671
|
});
|
|
656
672
|
app.post("/projects/:project/v1/traces", async (req, reply) => {
|
|
@@ -2250,6 +2266,7 @@ var import_yaml = require("yaml");
|
|
|
2250
2266
|
var import_types3 = require("@neat.is/types");
|
|
2251
2267
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2252
2268
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2269
|
+
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
2253
2270
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2254
2271
|
"node_modules",
|
|
2255
2272
|
".git",
|
|
@@ -2289,6 +2306,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2289
2306
|
function isConfigFile(name) {
|
|
2290
2307
|
const ext = import_node_path3.default.extname(name);
|
|
2291
2308
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2309
|
+
if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
|
|
2292
2310
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2293
2311
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2294
2312
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -4865,10 +4883,15 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4865
4883
|
}
|
|
4866
4884
|
if (!entry) return null;
|
|
4867
4885
|
try {
|
|
4868
|
-
const
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4886
|
+
const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
|
|
4887
|
+
let pos = entry.consumer.originalPositionFor({ line: queryLine, column: 0 });
|
|
4888
|
+
if (!pos || !pos.source) {
|
|
4889
|
+
pos = entry.consumer.originalPositionFor({
|
|
4890
|
+
line: queryLine,
|
|
4891
|
+
column: 0,
|
|
4892
|
+
bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
|
|
4893
|
+
});
|
|
4894
|
+
}
|
|
4872
4895
|
if (!pos || !pos.source) return null;
|
|
4873
4896
|
const root = entry.consumer.sourceRoot ?? "";
|
|
4874
4897
|
const resolved = import_node_path8.default.resolve(entry.dir, root, pos.source);
|
|
@@ -4877,6 +4900,9 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4877
4900
|
return null;
|
|
4878
4901
|
}
|
|
4879
4902
|
}
|
|
4903
|
+
function hasAdjacentSourceMap(absFilepath) {
|
|
4904
|
+
return sourceMapCache.get(absFilepath) != null;
|
|
4905
|
+
}
|
|
4880
4906
|
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
4881
4907
|
const filepath = codeFilepathOf(span.attributes);
|
|
4882
4908
|
if (filepath === void 0) return null;
|
|
@@ -4892,7 +4918,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
4892
4918
|
}
|
|
4893
4919
|
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
4894
4920
|
if (!relPath) return null;
|
|
4895
|
-
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
4921
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
|
|
4896
4922
|
warnNoSourceMaps(serviceNode.name);
|
|
4897
4923
|
}
|
|
4898
4924
|
const fn = codeFunctionOf(span.attributes);
|
|
@@ -5168,7 +5194,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
5168
5194
|
function frontierIdFor(host) {
|
|
5169
5195
|
return (0, import_types8.frontierId)(host);
|
|
5170
5196
|
}
|
|
5171
|
-
function
|
|
5197
|
+
function resolveFusedServiceId(graph, serviceName, env) {
|
|
5172
5198
|
const id = (0, import_types8.serviceId)(serviceName, env);
|
|
5173
5199
|
if (graph.hasNode(id)) return id;
|
|
5174
5200
|
const wanted = serviceName.toLowerCase();
|
|
@@ -5178,17 +5204,21 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
5178
5204
|
if (svc.discoveredVia === "otel") return false;
|
|
5179
5205
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
5180
5206
|
});
|
|
5181
|
-
|
|
5207
|
+
return extractedId ?? id;
|
|
5208
|
+
}
|
|
5209
|
+
function ensureServiceNode(graph, serviceName, env) {
|
|
5210
|
+
const resolved = resolveFusedServiceId(graph, serviceName, env);
|
|
5211
|
+
if (graph.hasNode(resolved)) return resolved;
|
|
5182
5212
|
const node = {
|
|
5183
|
-
id,
|
|
5213
|
+
id: resolved,
|
|
5184
5214
|
type: import_types8.NodeType.ServiceNode,
|
|
5185
5215
|
name: serviceName,
|
|
5186
5216
|
language: "unknown",
|
|
5187
5217
|
discoveredVia: "otel",
|
|
5188
5218
|
...env !== "unknown" ? { env } : {}
|
|
5189
5219
|
};
|
|
5190
|
-
graph.addNode(
|
|
5191
|
-
return
|
|
5220
|
+
graph.addNode(resolved, node);
|
|
5221
|
+
return resolved;
|
|
5192
5222
|
}
|
|
5193
5223
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
5194
5224
|
const id = (0, import_types8.infraId)(kind, name);
|
|
@@ -5381,8 +5411,23 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5381
5411
|
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5382
5412
|
await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5383
5413
|
}
|
|
5414
|
+
async function appendConnectorIncident(errorsPath, input) {
|
|
5415
|
+
const ev = {
|
|
5416
|
+
id: input.id,
|
|
5417
|
+
timestamp: input.timestamp,
|
|
5418
|
+
service: input.service,
|
|
5419
|
+
traceId: input.id,
|
|
5420
|
+
spanId: input.id,
|
|
5421
|
+
errorType: input.errorType,
|
|
5422
|
+
errorMessage: input.errorMessage,
|
|
5423
|
+
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
5424
|
+
affectedNode: input.affectedNode
|
|
5425
|
+
};
|
|
5426
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
|
|
5427
|
+
await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5428
|
+
}
|
|
5384
5429
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5385
|
-
const sid = (0, import_types8.serviceId)(span.service, span.env);
|
|
5430
|
+
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
|
|
5386
5431
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
5387
5432
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
5388
5433
|
if (callSite) {
|
|
@@ -6784,7 +6829,7 @@ function disambiguate(defs) {
|
|
|
6784
6829
|
}
|
|
6785
6830
|
async function addSymbols(graph, services) {
|
|
6786
6831
|
const parsers = /* @__PURE__ */ new Map();
|
|
6787
|
-
const
|
|
6832
|
+
const parserForExt5 = (ext) => {
|
|
6788
6833
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6789
6834
|
if (!grammar) return null;
|
|
6790
6835
|
let parser = parsers.get(ext);
|
|
@@ -6800,7 +6845,7 @@ async function addSymbols(graph, services) {
|
|
|
6800
6845
|
for (const service of services) {
|
|
6801
6846
|
const files = await loadSourceFiles(service.dir);
|
|
6802
6847
|
for (const file of files) {
|
|
6803
|
-
const parser =
|
|
6848
|
+
const parser = parserForExt5(import_node_path17.default.extname(file.path));
|
|
6804
6849
|
if (!parser) continue;
|
|
6805
6850
|
const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
|
|
6806
6851
|
let defs;
|
|
@@ -6952,7 +6997,7 @@ function stringInner(node) {
|
|
|
6952
6997
|
}
|
|
6953
6998
|
async function addSymbolEdges(graph, services) {
|
|
6954
6999
|
const parsers = /* @__PURE__ */ new Map();
|
|
6955
|
-
const
|
|
7000
|
+
const parserForExt5 = (ext) => {
|
|
6956
7001
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6957
7002
|
if (!grammar) return null;
|
|
6958
7003
|
let parser = parsers.get(ext);
|
|
@@ -6969,7 +7014,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6969
7014
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6970
7015
|
const files = await loadSourceFiles(service.dir);
|
|
6971
7016
|
for (const file of files) {
|
|
6972
|
-
const parser =
|
|
7017
|
+
const parser = parserForExt5(import_node_path18.default.extname(file.path));
|
|
6973
7018
|
if (!parser) continue;
|
|
6974
7019
|
const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
|
|
6975
7020
|
const fileDir = import_node_path18.default.dirname(file.path);
|
|
@@ -7231,7 +7276,7 @@ function firstReferenceLines(root, wanted) {
|
|
|
7231
7276
|
}
|
|
7232
7277
|
async function addServerActions(graph, services) {
|
|
7233
7278
|
const parsers = /* @__PURE__ */ new Map();
|
|
7234
|
-
const
|
|
7279
|
+
const parserForExt5 = (ext) => {
|
|
7235
7280
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
7236
7281
|
if (!grammar) return null;
|
|
7237
7282
|
let parser = parsers.get(ext);
|
|
@@ -7254,7 +7299,7 @@ async function addServerActions(graph, services) {
|
|
|
7254
7299
|
const files = await loadSourceFiles(service.dir);
|
|
7255
7300
|
for (const file of files) {
|
|
7256
7301
|
if (isTestPath(file.path)) continue;
|
|
7257
|
-
const parser =
|
|
7302
|
+
const parser = parserForExt5(import_node_path19.default.extname(file.path));
|
|
7258
7303
|
if (!parser) continue;
|
|
7259
7304
|
const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
|
|
7260
7305
|
let root;
|
|
@@ -7317,7 +7362,7 @@ async function addServerActions(graph, services) {
|
|
|
7317
7362
|
}
|
|
7318
7363
|
for (const file of files) {
|
|
7319
7364
|
if (isTestPath(file.path)) continue;
|
|
7320
|
-
const parser =
|
|
7365
|
+
const parser = parserForExt5(import_node_path19.default.extname(file.path));
|
|
7321
7366
|
if (!parser) continue;
|
|
7322
7367
|
const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
|
|
7323
7368
|
const fileDir = import_node_path19.default.dirname(file.path);
|
|
@@ -8281,6 +8326,7 @@ init_cjs_shims();
|
|
|
8281
8326
|
var import_node_path30 = __toESM(require("path"), 1);
|
|
8282
8327
|
var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
|
|
8283
8328
|
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
8329
|
+
var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
|
|
8284
8330
|
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
8285
8331
|
var import_types20 = require("@neat.is/types");
|
|
8286
8332
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -8331,19 +8377,27 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
8331
8377
|
}
|
|
8332
8378
|
return out;
|
|
8333
8379
|
}
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8380
|
+
var GRAMMAR_BY_EXT2 = {
|
|
8381
|
+
".ts": import_tree_sitter_typescript2.default.typescript,
|
|
8382
|
+
".tsx": import_tree_sitter_typescript2.default.tsx,
|
|
8383
|
+
".js": import_tree_sitter_javascript4.default,
|
|
8384
|
+
".jsx": import_tree_sitter_javascript4.default,
|
|
8385
|
+
".mjs": import_tree_sitter_javascript4.default,
|
|
8386
|
+
".cjs": import_tree_sitter_javascript4.default,
|
|
8387
|
+
".py": import_tree_sitter_python3.default
|
|
8388
|
+
};
|
|
8389
|
+
function parserForExt(ext, cache) {
|
|
8390
|
+
const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
|
|
8391
|
+
let parser = cache.get(grammar);
|
|
8392
|
+
if (!parser) {
|
|
8393
|
+
parser = new import_tree_sitter6.default();
|
|
8394
|
+
parser.setLanguage(grammar);
|
|
8395
|
+
cache.set(grammar, parser);
|
|
8396
|
+
}
|
|
8397
|
+
return parser;
|
|
8343
8398
|
}
|
|
8344
8399
|
async function addHttpCallEdges(graph, services) {
|
|
8345
|
-
const
|
|
8346
|
-
const pyParser = makePyParser3();
|
|
8400
|
+
const parserCache = /* @__PURE__ */ new Map();
|
|
8347
8401
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8348
8402
|
let nodesAdded = 0;
|
|
8349
8403
|
let edgesAdded = 0;
|
|
@@ -8352,7 +8406,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8352
8406
|
const seen = /* @__PURE__ */ new Set();
|
|
8353
8407
|
for (const file of files) {
|
|
8354
8408
|
if (isTestPath(file.path)) continue;
|
|
8355
|
-
const parser = import_node_path30.default.extname(file.path)
|
|
8409
|
+
const parser = parserForExt(import_node_path30.default.extname(file.path), parserCache);
|
|
8356
8410
|
let sites;
|
|
8357
8411
|
try {
|
|
8358
8412
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8425,7 +8479,7 @@ function parseSource5(parser, source) {
|
|
|
8425
8479
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
|
|
8426
8480
|
);
|
|
8427
8481
|
}
|
|
8428
|
-
function
|
|
8482
|
+
function makeJsParser3() {
|
|
8429
8483
|
const p = new import_tree_sitter7.default();
|
|
8430
8484
|
p.setLanguage(import_tree_sitter_javascript5.default);
|
|
8431
8485
|
return p;
|
|
@@ -8603,7 +8657,7 @@ function findRoute(entries, method, normalizedPath) {
|
|
|
8603
8657
|
);
|
|
8604
8658
|
}
|
|
8605
8659
|
async function addRouteCallEdges(graph, services) {
|
|
8606
|
-
const jsParser =
|
|
8660
|
+
const jsParser = makeJsParser3();
|
|
8607
8661
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8608
8662
|
const routeIndex = buildRouteIndex(graph);
|
|
8609
8663
|
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
@@ -8995,7 +9049,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
|
|
|
8995
9049
|
var import_types27 = require("@neat.is/types");
|
|
8996
9050
|
var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
|
|
8997
9051
|
var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
|
|
8998
|
-
function
|
|
9052
|
+
function parserForExt2(ext) {
|
|
8999
9053
|
const p = new import_tree_sitter8.default();
|
|
9000
9054
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
|
|
9001
9055
|
return p;
|
|
@@ -9160,7 +9214,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9160
9214
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
9161
9215
|
if (!hasClient && !hasAdmin) return [];
|
|
9162
9216
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
9163
|
-
const tree = parseSource3(
|
|
9217
|
+
const tree = parseSource3(parserForExt2(import_node_path37.default.extname(file.path)), file.content);
|
|
9164
9218
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
9165
9219
|
const collLine = /* @__PURE__ */ new Map();
|
|
9166
9220
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -9577,7 +9631,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9577
9631
|
var import_types29 = require("@neat.is/types");
|
|
9578
9632
|
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
9579
9633
|
var PARSE_CHUNK6 = 16384;
|
|
9580
|
-
function
|
|
9634
|
+
function makePyParser3() {
|
|
9581
9635
|
const p = new import_tree_sitter9.default();
|
|
9582
9636
|
p.setLanguage(import_tree_sitter_python4.default);
|
|
9583
9637
|
return p;
|
|
@@ -9684,7 +9738,7 @@ function foreignKeyParentTable(call) {
|
|
|
9684
9738
|
}
|
|
9685
9739
|
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
9686
9740
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9687
|
-
const tree = parseSource6(
|
|
9741
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9688
9742
|
const out = [];
|
|
9689
9743
|
const seen = /* @__PURE__ */ new Set();
|
|
9690
9744
|
walk3(tree.rootNode, (node) => {
|
|
@@ -9721,7 +9775,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
9721
9775
|
}
|
|
9722
9776
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
9723
9777
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9724
|
-
const tree = parseSource6(
|
|
9778
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9725
9779
|
const out = [];
|
|
9726
9780
|
const seen = /* @__PURE__ */ new Set();
|
|
9727
9781
|
const push = (name, line, columns) => {
|
|
@@ -9773,7 +9827,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
9773
9827
|
function buildSqlalchemyModelRegistry(files) {
|
|
9774
9828
|
const table = /* @__PURE__ */ new Map();
|
|
9775
9829
|
const ambiguous = /* @__PURE__ */ new Set();
|
|
9776
|
-
const parser =
|
|
9830
|
+
const parser = makePyParser3();
|
|
9777
9831
|
for (const file of files) {
|
|
9778
9832
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
|
|
9779
9833
|
const tree = parseSource6(parser, file.content);
|
|
@@ -9822,7 +9876,7 @@ function importsModelName(content, name) {
|
|
|
9822
9876
|
function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
9823
9877
|
const registry = buildSqlalchemyModelRegistry(files);
|
|
9824
9878
|
if (registry.size === 0) return [];
|
|
9825
|
-
const parser =
|
|
9879
|
+
const parser = makePyParser3();
|
|
9826
9880
|
const out = [];
|
|
9827
9881
|
const seen = /* @__PURE__ */ new Set();
|
|
9828
9882
|
for (const file of files) {
|
|
@@ -9860,7 +9914,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9860
9914
|
var import_types30 = require("@neat.is/types");
|
|
9861
9915
|
var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
|
|
9862
9916
|
var PARSE_CHUNK7 = 16384;
|
|
9863
|
-
function
|
|
9917
|
+
function makePyParser4() {
|
|
9864
9918
|
const p = new import_tree_sitter10.default();
|
|
9865
9919
|
p.setLanguage(import_tree_sitter_python5.default);
|
|
9866
9920
|
return p;
|
|
@@ -9921,7 +9975,7 @@ function readMeta(body) {
|
|
|
9921
9975
|
}
|
|
9922
9976
|
function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
9923
9977
|
if (!DJANGO_IMPORT_RE.test(file.content)) return [];
|
|
9924
|
-
const tree = parseSource7(
|
|
9978
|
+
const tree = parseSource7(makePyParser4(), file.content);
|
|
9925
9979
|
const out = [];
|
|
9926
9980
|
const seen = /* @__PURE__ */ new Set();
|
|
9927
9981
|
const defaultAppLabel = import_node_path40.default.basename(import_node_path40.default.dirname(file.path));
|
|
@@ -9956,7 +10010,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
|
|
|
9956
10010
|
var import_types31 = require("@neat.is/types");
|
|
9957
10011
|
var DRIZZLE_IMPORT_RE = /drizzle-orm/;
|
|
9958
10012
|
var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
|
|
9959
|
-
function
|
|
10013
|
+
function parserForExt3(ext) {
|
|
9960
10014
|
const p = new import_tree_sitter11.default();
|
|
9961
10015
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
|
|
9962
10016
|
return p;
|
|
@@ -10028,7 +10082,7 @@ function columnsFromObject(obj) {
|
|
|
10028
10082
|
}
|
|
10029
10083
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10030
10084
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10031
|
-
const tree = parseSource3(
|
|
10085
|
+
const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
|
|
10032
10086
|
const out = [];
|
|
10033
10087
|
const seen = /* @__PURE__ */ new Set();
|
|
10034
10088
|
const walk9 = (node) => {
|
|
@@ -10117,7 +10171,7 @@ function referencesTargetVar(call) {
|
|
|
10117
10171
|
}
|
|
10118
10172
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10119
10173
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10120
|
-
const tree = parseSource3(
|
|
10174
|
+
const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
|
|
10121
10175
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10122
10176
|
const out = [];
|
|
10123
10177
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11185,8 +11239,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11185
11239
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11186
11240
|
var import_types35 = require("@neat.is/types");
|
|
11187
11241
|
init_otel();
|
|
11188
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11189
11242
|
var PARSE_CHUNK10 = 16384;
|
|
11243
|
+
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
11244
|
+
"Query",
|
|
11245
|
+
"QueryContext",
|
|
11246
|
+
"QueryRow",
|
|
11247
|
+
"QueryRowContext",
|
|
11248
|
+
"Exec",
|
|
11249
|
+
"ExecContext",
|
|
11250
|
+
"Prepare",
|
|
11251
|
+
"PrepareContext"
|
|
11252
|
+
]);
|
|
11253
|
+
var SQLX_METHODS = /* @__PURE__ */ new Set([
|
|
11254
|
+
"Get",
|
|
11255
|
+
"Select",
|
|
11256
|
+
"Queryx",
|
|
11257
|
+
"QueryRowx",
|
|
11258
|
+
"NamedExec",
|
|
11259
|
+
"NamedQuery",
|
|
11260
|
+
"MustExec",
|
|
11261
|
+
"Preparex",
|
|
11262
|
+
"GetContext",
|
|
11263
|
+
"SelectContext"
|
|
11264
|
+
]);
|
|
11265
|
+
var DATABASE_SQL_IMPORT = "database/sql";
|
|
11266
|
+
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
11267
|
+
function makeGoParser3() {
|
|
11268
|
+
const p = new import_tree_sitter14.default();
|
|
11269
|
+
p.setLanguage(import_tree_sitter_go3.default);
|
|
11270
|
+
return p;
|
|
11271
|
+
}
|
|
11272
|
+
function parseSource10(parser, source) {
|
|
11273
|
+
return parser.parse(
|
|
11274
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK10)
|
|
11275
|
+
);
|
|
11276
|
+
}
|
|
11190
11277
|
function walk7(node, visit) {
|
|
11191
11278
|
visit(node);
|
|
11192
11279
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11194,25 +11281,54 @@ function walk7(node, visit) {
|
|
|
11194
11281
|
if (child) walk7(child, visit);
|
|
11195
11282
|
}
|
|
11196
11283
|
}
|
|
11284
|
+
function goStringLiteralValue(node) {
|
|
11285
|
+
if (!node) return null;
|
|
11286
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
11287
|
+
const t = node.text;
|
|
11288
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
11289
|
+
}
|
|
11290
|
+
return null;
|
|
11291
|
+
}
|
|
11292
|
+
function goImportsAny(root, names) {
|
|
11293
|
+
let found = false;
|
|
11294
|
+
walk7(root, (node) => {
|
|
11295
|
+
if (found || node.type !== "import_spec") return;
|
|
11296
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
11297
|
+
const value = goStringLiteralValue(node.namedChild(i));
|
|
11298
|
+
if (value !== null && names.has(value)) found = true;
|
|
11299
|
+
}
|
|
11300
|
+
});
|
|
11301
|
+
return found;
|
|
11302
|
+
}
|
|
11303
|
+
function firstStringLiteralArg(argsNode) {
|
|
11304
|
+
if (!argsNode) return null;
|
|
11305
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
11306
|
+
const value = goStringLiteralValue(argsNode.namedChild(i));
|
|
11307
|
+
if (value !== null) return value;
|
|
11308
|
+
}
|
|
11309
|
+
return null;
|
|
11310
|
+
}
|
|
11197
11311
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11198
11312
|
if (import_node_path47.default.extname(file.path) !== ".go") return [];
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
const
|
|
11202
|
-
|
|
11203
|
-
);
|
|
11313
|
+
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11314
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11315
|
+
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
11316
|
+
const importsSqlx = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([SQLX_IMPORT]));
|
|
11317
|
+
if (!importsDatabaseSql && !importsSqlx) return [];
|
|
11204
11318
|
const out = [];
|
|
11205
11319
|
walk7(tree.rootNode, (node) => {
|
|
11206
11320
|
if (node.type !== "call_expression") return;
|
|
11207
11321
|
const fn = node.childForFieldName("function");
|
|
11208
11322
|
if (fn?.type !== "selector_expression") return;
|
|
11209
11323
|
const method = fn.childForFieldName("field")?.text;
|
|
11210
|
-
if (!method
|
|
11211
|
-
const
|
|
11212
|
-
if (!
|
|
11213
|
-
const sql =
|
|
11324
|
+
if (!method) return;
|
|
11325
|
+
const recognized = DATABASE_SQL_METHODS.has(method) || importsSqlx && SQLX_METHODS.has(method);
|
|
11326
|
+
if (!recognized) return;
|
|
11327
|
+
const sql = firstStringLiteralArg(node.childForFieldName("arguments"));
|
|
11328
|
+
if (sql === null) return;
|
|
11214
11329
|
const table = tableFromSqlStatement(sql);
|
|
11215
11330
|
if (!table) return;
|
|
11331
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11216
11332
|
const line = node.startPosition.row + 1;
|
|
11217
11333
|
out.push({
|
|
11218
11334
|
infraId: (0, import_types35.infraId)("sql-table", table),
|
|
@@ -11220,7 +11336,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11220
11336
|
kind: "sql-table",
|
|
11221
11337
|
edgeType: "CALLS",
|
|
11222
11338
|
confidenceKind: "verified-call-site",
|
|
11223
|
-
|
|
11339
|
+
...columns.length > 0 ? { columns } : {},
|
|
11340
|
+
evidence: {
|
|
11341
|
+
file: toPosix(import_node_path47.default.relative(serviceDir, file.path)),
|
|
11342
|
+
line,
|
|
11343
|
+
snippet: snippet(file.content, line)
|
|
11344
|
+
}
|
|
11224
11345
|
});
|
|
11225
11346
|
});
|
|
11226
11347
|
return out;
|
|
@@ -11234,12 +11355,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11234
11355
|
var import_types36 = require("@neat.is/types");
|
|
11235
11356
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11236
11357
|
var PARSE_CHUNK11 = 16384;
|
|
11237
|
-
function
|
|
11358
|
+
function makeGoParser4() {
|
|
11238
11359
|
const p = new import_tree_sitter15.default();
|
|
11239
11360
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11240
11361
|
return p;
|
|
11241
11362
|
}
|
|
11242
|
-
function
|
|
11363
|
+
function parseSource11(parser, source) {
|
|
11243
11364
|
return parser.parse(
|
|
11244
11365
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11245
11366
|
);
|
|
@@ -11663,7 +11784,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11663
11784
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11664
11785
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11665
11786
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11666
|
-
const tree =
|
|
11787
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11667
11788
|
const { structs, models, tableFor } = analyze(tree);
|
|
11668
11789
|
const out = [];
|
|
11669
11790
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -11694,7 +11815,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11694
11815
|
function gormForeignKeys(file, serviceDir) {
|
|
11695
11816
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11696
11817
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11697
|
-
const tree =
|
|
11818
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11698
11819
|
const { structs, models, tableFor } = analyze(tree);
|
|
11699
11820
|
const out = [];
|
|
11700
11821
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12824,7 +12945,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
|
|
|
12824
12945
|
var import_types47 = require("@neat.is/types");
|
|
12825
12946
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
12826
12947
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
12827
|
-
function
|
|
12948
|
+
function parserForExt4(ext) {
|
|
12828
12949
|
const p = new import_tree_sitter16.default();
|
|
12829
12950
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
|
|
12830
12951
|
return p;
|
|
@@ -12913,7 +13034,7 @@ function topLevelSchemas(root) {
|
|
|
12913
13034
|
}
|
|
12914
13035
|
function zodShapesFromFile(file, serviceDir) {
|
|
12915
13036
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
12916
|
-
const tree = parseSource3(
|
|
13037
|
+
const tree = parseSource3(parserForExt4(import_node_path57.default.extname(file.path)), file.content);
|
|
12917
13038
|
const out = [];
|
|
12918
13039
|
const seen = /* @__PURE__ */ new Set();
|
|
12919
13040
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13437,7 +13558,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
13437
13558
|
init_cjs_shims();
|
|
13438
13559
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
13439
13560
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
13440
|
-
var
|
|
13561
|
+
var import_types85 = require("@neat.is/types");
|
|
13441
13562
|
|
|
13442
13563
|
// src/extend/index.ts
|
|
13443
13564
|
init_cjs_shims();
|
|
@@ -14943,6 +15064,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
14943
15064
|
unresolved++;
|
|
14944
15065
|
continue;
|
|
14945
15066
|
}
|
|
15067
|
+
if (signal.incident) {
|
|
15068
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
15069
|
+
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
15070
|
+
unresolved++;
|
|
15071
|
+
continue;
|
|
15072
|
+
}
|
|
15073
|
+
await appendConnectorIncident(ctx.errorsPath, {
|
|
15074
|
+
id: signal.incident.id,
|
|
15075
|
+
timestamp: signal.incident.timestamp,
|
|
15076
|
+
service: signal.incident.service,
|
|
15077
|
+
errorType: signal.incident.errorType,
|
|
15078
|
+
errorMessage: signal.incident.errorMessage,
|
|
15079
|
+
...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
|
|
15080
|
+
affectedNode: resolved.targetNodeId
|
|
15081
|
+
});
|
|
15082
|
+
continue;
|
|
15083
|
+
}
|
|
14946
15084
|
if (resolved.ensureInfraNode) {
|
|
14947
15085
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
14948
15086
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -17177,6 +17315,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
17177
17315
|
};
|
|
17178
17316
|
}
|
|
17179
17317
|
|
|
17318
|
+
// src/connectors/eas/index.ts
|
|
17319
|
+
init_cjs_shims();
|
|
17320
|
+
|
|
17321
|
+
// src/connectors/eas/client.ts
|
|
17322
|
+
init_cjs_shims();
|
|
17323
|
+
|
|
17324
|
+
// src/connectors/eas/types.ts
|
|
17325
|
+
init_cjs_shims();
|
|
17326
|
+
function readEasCredentials(raw) {
|
|
17327
|
+
const token = raw["token"];
|
|
17328
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
17329
|
+
throw new Error("eas connector: credentials.token (EXPO_TOKEN) must be a non-empty string");
|
|
17330
|
+
}
|
|
17331
|
+
return { token };
|
|
17332
|
+
}
|
|
17333
|
+
var EAS_STATUS_ERRORED = "ERRORED";
|
|
17334
|
+
var TRANSIENT_BUILD_PHASES = /* @__PURE__ */ new Set([
|
|
17335
|
+
"SPIN_UP_BUILDER",
|
|
17336
|
+
"PREPARE_CREDENTIALS",
|
|
17337
|
+
"RESTORE_CACHE",
|
|
17338
|
+
"UPLOAD_APPLICATION_ARCHIVE"
|
|
17339
|
+
]);
|
|
17340
|
+
var INTERNAL_ERROR_CODE = /INTERNAL_SERVER_ERROR|EAS_BUILD_.*INTERNAL|_INTERNAL_ERROR|UNKNOWN_ERROR/i;
|
|
17341
|
+
function isTransientFailure(err) {
|
|
17342
|
+
if (!err) return false;
|
|
17343
|
+
const phase = typeof err.buildPhase === "string" ? err.buildPhase : void 0;
|
|
17344
|
+
if (phase) {
|
|
17345
|
+
if (TRANSIENT_BUILD_PHASES.has(phase)) return true;
|
|
17346
|
+
if (/^SPIN_UP|_CREDENTIALS$|^RESTORE_CACHE/i.test(phase)) return true;
|
|
17347
|
+
}
|
|
17348
|
+
const code = typeof err.errorCode === "string" ? err.errorCode : void 0;
|
|
17349
|
+
if (code && INTERNAL_ERROR_CODE.test(code)) return true;
|
|
17350
|
+
return false;
|
|
17351
|
+
}
|
|
17352
|
+
var FIELD_SEP3 = "\0";
|
|
17353
|
+
var EAS_TARGET_KIND = "eas-build";
|
|
17354
|
+
function packEasTargetName(identity) {
|
|
17355
|
+
return [identity.serviceName, identity.phase].join(FIELD_SEP3);
|
|
17356
|
+
}
|
|
17357
|
+
function parseEasTargetName(targetName) {
|
|
17358
|
+
const sep = targetName.indexOf(FIELD_SEP3);
|
|
17359
|
+
if (sep === -1) return null;
|
|
17360
|
+
const serviceName = targetName.slice(0, sep);
|
|
17361
|
+
const phase = targetName.slice(sep + 1);
|
|
17362
|
+
if (!serviceName) return null;
|
|
17363
|
+
return { serviceName, phase };
|
|
17364
|
+
}
|
|
17365
|
+
|
|
17366
|
+
// src/connectors/eas/client.ts
|
|
17367
|
+
var DEFAULT_EAS_API_URL = "https://api.expo.dev/graphql";
|
|
17368
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
17369
|
+
var DEFAULT_MAX_PAGES = 10;
|
|
17370
|
+
var DEFAULT_MAX_LOG_BYTES = 16 * 1024;
|
|
17371
|
+
var DEFAULT_MAX_LOOKBACK_MS6 = 7 * 24 * 60 * 60 * 1e3;
|
|
17372
|
+
var BUILDS_QUERY = `
|
|
17373
|
+
query NeatEasErroredBuilds($appId: String!, $offset: Int!, $limit: Int!) {
|
|
17374
|
+
app {
|
|
17375
|
+
byId(appId: $appId) {
|
|
17376
|
+
builds(offset: $offset, limit: $limit, filter: { status: ERRORED }) {
|
|
17377
|
+
id
|
|
17378
|
+
status
|
|
17379
|
+
platform
|
|
17380
|
+
buildProfile
|
|
17381
|
+
gitCommitHash
|
|
17382
|
+
gitCommitMessage
|
|
17383
|
+
gitRef
|
|
17384
|
+
isGitWorkingTreeDirty
|
|
17385
|
+
createdAt
|
|
17386
|
+
completedAt
|
|
17387
|
+
error {
|
|
17388
|
+
buildPhase
|
|
17389
|
+
errorCode
|
|
17390
|
+
message
|
|
17391
|
+
docsUrl
|
|
17392
|
+
}
|
|
17393
|
+
logFileUrls
|
|
17394
|
+
}
|
|
17395
|
+
}
|
|
17396
|
+
}
|
|
17397
|
+
}
|
|
17398
|
+
`;
|
|
17399
|
+
async function easGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
17400
|
+
const res = await junctionFetch(
|
|
17401
|
+
apiUrl,
|
|
17402
|
+
{
|
|
17403
|
+
method: "POST",
|
|
17404
|
+
headers: {
|
|
17405
|
+
"Content-Type": "application/json",
|
|
17406
|
+
...bearerAuthHeader(token)
|
|
17407
|
+
},
|
|
17408
|
+
body: JSON.stringify({ query, variables })
|
|
17409
|
+
},
|
|
17410
|
+
// accountKey: the Expo app id — the per-(provider, accountKey) rate-limit
|
|
17411
|
+
// bucket (ADR-131), the closest thing this connector carries to "one account".
|
|
17412
|
+
{ provider: "eas", accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
17413
|
+
);
|
|
17414
|
+
if (!res.ok) {
|
|
17415
|
+
throw new Error(`Expo GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
17416
|
+
}
|
|
17417
|
+
const body = await res.json();
|
|
17418
|
+
if (body.errors && body.errors.length > 0) {
|
|
17419
|
+
throw new Error(`Expo GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
|
|
17420
|
+
}
|
|
17421
|
+
if (!body.data) throw new Error("Expo GraphQL response carried no data");
|
|
17422
|
+
return body.data;
|
|
17423
|
+
}
|
|
17424
|
+
async function fetchErroredBuilds(token, config, fetchImpl) {
|
|
17425
|
+
const apiUrl = config.apiUrl ?? DEFAULT_EAS_API_URL;
|
|
17426
|
+
const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
17427
|
+
const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;
|
|
17428
|
+
const out = [];
|
|
17429
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17430
|
+
for (let page = 0; page < maxPages; page++) {
|
|
17431
|
+
const data = await easGraphQL(
|
|
17432
|
+
apiUrl,
|
|
17433
|
+
token,
|
|
17434
|
+
BUILDS_QUERY,
|
|
17435
|
+
{ appId: config.appId, offset: page * pageSize, limit: pageSize },
|
|
17436
|
+
config.appId,
|
|
17437
|
+
fetchImpl
|
|
17438
|
+
);
|
|
17439
|
+
const builds = data.app?.byId?.builds;
|
|
17440
|
+
if (!Array.isArray(builds)) break;
|
|
17441
|
+
let added = 0;
|
|
17442
|
+
for (const b of builds) {
|
|
17443
|
+
if (!b || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
17444
|
+
if (b.status !== EAS_STATUS_ERRORED) continue;
|
|
17445
|
+
if (seen.has(b.id)) continue;
|
|
17446
|
+
seen.add(b.id);
|
|
17447
|
+
out.push(b);
|
|
17448
|
+
added++;
|
|
17449
|
+
}
|
|
17450
|
+
if (builds.length < pageSize) break;
|
|
17451
|
+
if (added === 0) break;
|
|
17452
|
+
}
|
|
17453
|
+
return out;
|
|
17454
|
+
}
|
|
17455
|
+
async function fetchBuildLogs(logFileUrls, maxBytes = DEFAULT_MAX_LOG_BYTES, fetchImpl) {
|
|
17456
|
+
if (!Array.isArray(logFileUrls) || logFileUrls.length === 0) return "";
|
|
17457
|
+
const doFetch = fetchImpl ?? fetch;
|
|
17458
|
+
const chunks = [];
|
|
17459
|
+
for (const url of logFileUrls) {
|
|
17460
|
+
if (typeof url !== "string" || url.length === 0) continue;
|
|
17461
|
+
try {
|
|
17462
|
+
const res = await doFetch(url);
|
|
17463
|
+
if (!res.ok) continue;
|
|
17464
|
+
chunks.push(await res.text());
|
|
17465
|
+
} catch {
|
|
17466
|
+
}
|
|
17467
|
+
}
|
|
17468
|
+
const joined = chunks.join("\n");
|
|
17469
|
+
return joined.length > maxBytes ? joined.slice(joined.length - maxBytes) : joined;
|
|
17470
|
+
}
|
|
17471
|
+
|
|
17472
|
+
// src/connectors/eas/map.ts
|
|
17473
|
+
init_cjs_shims();
|
|
17474
|
+
function buildEventTime(build) {
|
|
17475
|
+
if (typeof build.completedAt === "string" && build.completedAt.length > 0) return build.completedAt;
|
|
17476
|
+
if (typeof build.createdAt === "string" && build.createdAt.length > 0) return build.createdAt;
|
|
17477
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17478
|
+
}
|
|
17479
|
+
function incidentMessage2(build) {
|
|
17480
|
+
const err = build.error ?? {};
|
|
17481
|
+
const phase = typeof err.buildPhase === "string" && err.buildPhase.length > 0 ? ` at ${err.buildPhase}` : "";
|
|
17482
|
+
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";
|
|
17483
|
+
let msg = `EAS build failed${phase}: ${detail}`;
|
|
17484
|
+
if (build.isGitWorkingTreeDirty === true) {
|
|
17485
|
+
msg += " (built from a dirty working tree \u2014 the commit may not represent what built)";
|
|
17486
|
+
}
|
|
17487
|
+
return msg;
|
|
17488
|
+
}
|
|
17489
|
+
function incidentAttributes(build) {
|
|
17490
|
+
const attrs = {};
|
|
17491
|
+
const err = build.error ?? {};
|
|
17492
|
+
const put = (k, v) => {
|
|
17493
|
+
if (typeof v === "string" && v.length === 0) return;
|
|
17494
|
+
if (v !== void 0 && v !== null) attrs[k] = v;
|
|
17495
|
+
};
|
|
17496
|
+
put("eas.buildId", build.id);
|
|
17497
|
+
put("eas.platform", build.platform ?? void 0);
|
|
17498
|
+
put("eas.buildProfile", build.buildProfile ?? void 0);
|
|
17499
|
+
put("eas.buildPhase", err.buildPhase ?? void 0);
|
|
17500
|
+
put("eas.errorCode", err.errorCode ?? void 0);
|
|
17501
|
+
put("eas.docsUrl", err.docsUrl ?? void 0);
|
|
17502
|
+
put("eas.gitCommitHash", build.gitCommitHash ?? void 0);
|
|
17503
|
+
put("eas.gitRef", build.gitRef ?? void 0);
|
|
17504
|
+
put("eas.gitCommitMessage", build.gitCommitMessage ?? void 0);
|
|
17505
|
+
if (typeof build.isGitWorkingTreeDirty === "boolean") {
|
|
17506
|
+
attrs["eas.gitWorkingTreeDirty"] = build.isGitWorkingTreeDirty;
|
|
17507
|
+
if (build.isGitWorkingTreeDirty) attrs["eas.confidence"] = "low";
|
|
17508
|
+
}
|
|
17509
|
+
put("eas.createdAt", build.createdAt ?? void 0);
|
|
17510
|
+
put("eas.completedAt", build.completedAt ?? void 0);
|
|
17511
|
+
if (typeof build.logsText === "string" && build.logsText.length > 0) {
|
|
17512
|
+
attrs["eas.logs"] = build.logsText;
|
|
17513
|
+
}
|
|
17514
|
+
return attrs;
|
|
17515
|
+
}
|
|
17516
|
+
function mapBuildToSignal(build, serviceName) {
|
|
17517
|
+
if (!build || typeof build !== "object") return null;
|
|
17518
|
+
if (build.status !== EAS_STATUS_ERRORED) return null;
|
|
17519
|
+
if (!build.error) return null;
|
|
17520
|
+
if (isTransientFailure(build.error)) return null;
|
|
17521
|
+
const timestamp = buildEventTime(build);
|
|
17522
|
+
const phase = typeof build.error.buildPhase === "string" ? build.error.buildPhase : "";
|
|
17523
|
+
return {
|
|
17524
|
+
targetKind: EAS_TARGET_KIND,
|
|
17525
|
+
targetName: packEasTargetName({ serviceName, phase }),
|
|
17526
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
17527
|
+
callCount: 0,
|
|
17528
|
+
errorCount: 0,
|
|
17529
|
+
lastObservedIso: timestamp,
|
|
17530
|
+
incident: {
|
|
17531
|
+
id: `eas:build:${build.id}`,
|
|
17532
|
+
timestamp,
|
|
17533
|
+
service: serviceName,
|
|
17534
|
+
errorType: "eas-build-failure",
|
|
17535
|
+
errorMessage: incidentMessage2(build),
|
|
17536
|
+
attributes: incidentAttributes(build)
|
|
17537
|
+
}
|
|
17538
|
+
};
|
|
17539
|
+
}
|
|
17540
|
+
function mapBuildsToSignals(builds, serviceName) {
|
|
17541
|
+
const out = [];
|
|
17542
|
+
for (const build of builds) {
|
|
17543
|
+
const signal = mapBuildToSignal(build, serviceName);
|
|
17544
|
+
if (signal) out.push(signal);
|
|
17545
|
+
}
|
|
17546
|
+
return out;
|
|
17547
|
+
}
|
|
17548
|
+
|
|
17549
|
+
// src/connectors/eas/resolve.ts
|
|
17550
|
+
init_cjs_shims();
|
|
17551
|
+
var import_types82 = require("@neat.is/types");
|
|
17552
|
+
var NO_ENV2 = "unknown";
|
|
17553
|
+
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17554
|
+
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
17555
|
+
"READ_APP_CONFIG",
|
|
17556
|
+
"CONFIGURE_EXPO_UPDATES",
|
|
17557
|
+
"CALCULATE_EXPO_UPDATES_RUNTIME_VERSION"
|
|
17558
|
+
]);
|
|
17559
|
+
function configBasenamesForPhase(phase) {
|
|
17560
|
+
if (EAS_JSON_PHASES.has(phase)) return ["eas.json"];
|
|
17561
|
+
if (APP_CONFIG_PHASES.has(phase)) return ["app.json", "app.config.json"];
|
|
17562
|
+
return [];
|
|
17563
|
+
}
|
|
17564
|
+
function configNodeService(graph, configNodeId) {
|
|
17565
|
+
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
17566
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
17567
|
+
if (edge.type !== import_types82.EdgeType.CONFIGURED_BY) continue;
|
|
17568
|
+
const parsed = (0, import_types82.parseFileId)(edge.source);
|
|
17569
|
+
if (parsed) return parsed.service;
|
|
17570
|
+
}
|
|
17571
|
+
return null;
|
|
17572
|
+
}
|
|
17573
|
+
function findConfigNode(graph, basenames, serviceName) {
|
|
17574
|
+
let scoped = null;
|
|
17575
|
+
let anyMatch = null;
|
|
17576
|
+
graph.forEachNode((id, attrs) => {
|
|
17577
|
+
if (scoped) return;
|
|
17578
|
+
const node = attrs;
|
|
17579
|
+
if (node.type !== import_types82.NodeType.ConfigNode) return;
|
|
17580
|
+
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17581
|
+
if (anyMatch === null) anyMatch = id;
|
|
17582
|
+
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
17583
|
+
});
|
|
17584
|
+
return scoped ?? anyMatch;
|
|
17585
|
+
}
|
|
17586
|
+
function createEasResolveTarget(graph) {
|
|
17587
|
+
return (signal) => {
|
|
17588
|
+
if (signal.targetKind !== EAS_TARGET_KIND) return null;
|
|
17589
|
+
const identity = parseEasTargetName(signal.targetName);
|
|
17590
|
+
if (!identity) return null;
|
|
17591
|
+
const { serviceName, phase } = identity;
|
|
17592
|
+
const basenames = configBasenamesForPhase(phase);
|
|
17593
|
+
if (basenames.length > 0) {
|
|
17594
|
+
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
17595
|
+
if (configNodeId) {
|
|
17596
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types82.EdgeType.CALLS };
|
|
17597
|
+
}
|
|
17598
|
+
}
|
|
17599
|
+
return {
|
|
17600
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
17601
|
+
serviceName,
|
|
17602
|
+
edgeType: import_types82.EdgeType.CALLS
|
|
17603
|
+
};
|
|
17604
|
+
};
|
|
17605
|
+
}
|
|
17606
|
+
|
|
17607
|
+
// src/connectors/eas/index.ts
|
|
17608
|
+
function isBuildSince(build, sinceIso) {
|
|
17609
|
+
const t = Date.parse(buildEventTime(build));
|
|
17610
|
+
const s = Date.parse(sinceIso);
|
|
17611
|
+
if (Number.isNaN(t) || Number.isNaN(s)) return true;
|
|
17612
|
+
return t > s;
|
|
17613
|
+
}
|
|
17614
|
+
function boundedSinceIso2(since, now, maxLookbackMs) {
|
|
17615
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
17616
|
+
if (!since) return floor.toISOString();
|
|
17617
|
+
const sinceMs = new Date(since).getTime();
|
|
17618
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
17619
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
17620
|
+
}
|
|
17621
|
+
var EasConnector = class {
|
|
17622
|
+
constructor(config, fetchImpl) {
|
|
17623
|
+
this.config = config;
|
|
17624
|
+
this.fetchImpl = fetchImpl;
|
|
17625
|
+
}
|
|
17626
|
+
config;
|
|
17627
|
+
fetchImpl;
|
|
17628
|
+
provider = "eas";
|
|
17629
|
+
async poll(ctx) {
|
|
17630
|
+
const creds = readEasCredentials(ctx.credentials);
|
|
17631
|
+
const serviceName = this.config.serviceName ?? this.config.appId;
|
|
17632
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS6;
|
|
17633
|
+
const sinceIso = boundedSinceIso2(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
17634
|
+
const builds = await fetchErroredBuilds(creds.token, this.config, this.fetchImpl);
|
|
17635
|
+
const fresh = builds.filter((b) => isBuildSince(b, sinceIso));
|
|
17636
|
+
const maxLogBytes = this.config.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
17637
|
+
for (const build of fresh) {
|
|
17638
|
+
build.logsText = await fetchBuildLogs(build.logFileUrls, maxLogBytes, this.fetchImpl);
|
|
17639
|
+
}
|
|
17640
|
+
return mapBuildsToSignals(fresh, serviceName);
|
|
17641
|
+
}
|
|
17642
|
+
};
|
|
17643
|
+
function createEasConnector(graph, config, fetchImpl) {
|
|
17644
|
+
return {
|
|
17645
|
+
connector: new EasConnector(config, fetchImpl),
|
|
17646
|
+
resolveTarget: createEasResolveTarget(graph)
|
|
17647
|
+
};
|
|
17648
|
+
}
|
|
17649
|
+
|
|
17180
17650
|
// src/connectors/registry.ts
|
|
17181
17651
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
17182
17652
|
async function authProbe(input) {
|
|
@@ -17464,6 +17934,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17464
17934
|
...fetchImpl ? { fetchImpl } : {}
|
|
17465
17935
|
});
|
|
17466
17936
|
}
|
|
17937
|
+
},
|
|
17938
|
+
eas: {
|
|
17939
|
+
provider: "eas",
|
|
17940
|
+
// The secret is a single robot-user EXPO_TOKEN; a single-string credential
|
|
17941
|
+
// maps to `token`. `appId` is non-secret config (connector-config.md §7.1).
|
|
17942
|
+
primaryCredentialKey: "token",
|
|
17943
|
+
requiredCredentialFields: ["token"],
|
|
17944
|
+
requiredOptionFields: ["appId"],
|
|
17945
|
+
build(graph, options) {
|
|
17946
|
+
return createEasConnector(graph, options);
|
|
17947
|
+
},
|
|
17948
|
+
// Runs the connector's real `builds` query at limit 1 — the exact read poll()
|
|
17949
|
+
// performs, minus the pages — so the probe checks both that the EXPO_TOKEN
|
|
17950
|
+
// authenticates and that this app id is reachable, the same probe-the-real-
|
|
17951
|
+
// query discipline Railway and Cloud Run use over a trivial `{ __typename }`.
|
|
17952
|
+
// A bad or wrong-scoped token comes back as an Expo GraphQL error, which
|
|
17953
|
+
// `fetchErroredBuilds` throws on, so it fails honestly here rather than
|
|
17954
|
+
// silently at the first poll.
|
|
17955
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
17956
|
+
const cfg = options;
|
|
17957
|
+
const appId = String(cfg.appId ?? "");
|
|
17958
|
+
if (!appId) return { ok: false, reason: "eas: appId is required to validate" };
|
|
17959
|
+
const probeConfig = {
|
|
17960
|
+
appId,
|
|
17961
|
+
pageSize: 1,
|
|
17962
|
+
maxPages: 1,
|
|
17963
|
+
...cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}
|
|
17964
|
+
};
|
|
17965
|
+
try {
|
|
17966
|
+
await fetchErroredBuilds(String(credentials.token ?? ""), probeConfig, fetchImpl);
|
|
17967
|
+
return { ok: true };
|
|
17968
|
+
} catch (err) {
|
|
17969
|
+
return { ok: false, reason: `eas auth check failed: ${err.message}` };
|
|
17970
|
+
}
|
|
17971
|
+
}
|
|
17467
17972
|
}
|
|
17468
17973
|
};
|
|
17469
17974
|
function vercelCredsFrom(credentials) {
|
|
@@ -17645,7 +18150,11 @@ async function startConnectorPolling(input) {
|
|
|
17645
18150
|
const stopFns = all.map(
|
|
17646
18151
|
(registration) => startConnectorPollLoop(
|
|
17647
18152
|
registration.connector,
|
|
17648
|
-
{
|
|
18153
|
+
{
|
|
18154
|
+
projectDir: input.projectDir,
|
|
18155
|
+
credentials: registration.credentials,
|
|
18156
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
18157
|
+
},
|
|
17649
18158
|
input.graph,
|
|
17650
18159
|
registration.resolveTarget,
|
|
17651
18160
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -17815,11 +18324,11 @@ function registerRoutes(scope, ctx) {
|
|
|
17815
18324
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17816
18325
|
const parsed = [];
|
|
17817
18326
|
for (const c of candidates) {
|
|
17818
|
-
const r =
|
|
18327
|
+
const r = import_types85.DivergenceTypeSchema.safeParse(c);
|
|
17819
18328
|
if (!r.success) {
|
|
17820
18329
|
return reply.code(400).send({
|
|
17821
18330
|
error: `unknown divergence type "${c}"`,
|
|
17822
|
-
allowed:
|
|
18331
|
+
allowed: import_types85.DivergenceTypeSchema.options
|
|
17823
18332
|
});
|
|
17824
18333
|
}
|
|
17825
18334
|
parsed.push(r.data);
|
|
@@ -17926,10 +18435,15 @@ function registerRoutes(scope, ctx) {
|
|
|
17926
18435
|
}
|
|
17927
18436
|
const reg = built.registration;
|
|
17928
18437
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18438
|
+
const incidentsPath = errorsPathFor(proj);
|
|
17929
18439
|
try {
|
|
17930
18440
|
const result = await ctx.runPoll(
|
|
17931
18441
|
reg.connector,
|
|
17932
|
-
{
|
|
18442
|
+
{
|
|
18443
|
+
projectDir: proj.scanPath ?? "",
|
|
18444
|
+
credentials: reg.credentials,
|
|
18445
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
18446
|
+
},
|
|
17933
18447
|
proj.graph,
|
|
17934
18448
|
reg.resolveTarget
|
|
17935
18449
|
);
|
|
@@ -18128,7 +18642,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18128
18642
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
18129
18643
|
let violations = await log.readAll();
|
|
18130
18644
|
if (req.query.severity) {
|
|
18131
|
-
const sev =
|
|
18645
|
+
const sev = import_types85.PolicySeveritySchema.safeParse(req.query.severity);
|
|
18132
18646
|
if (!sev.success) {
|
|
18133
18647
|
return reply.code(400).send({
|
|
18134
18648
|
error: "invalid severity",
|
|
@@ -18167,7 +18681,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18167
18681
|
scope.post("/policies/check", async (req, reply) => {
|
|
18168
18682
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
18169
18683
|
if (!proj) return;
|
|
18170
|
-
const parsed =
|
|
18684
|
+
const parsed = import_types85.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
18171
18685
|
if (!parsed.success) {
|
|
18172
18686
|
return reply.code(400).send({
|
|
18173
18687
|
error: "invalid /policies/check body",
|
|
@@ -18516,7 +19030,7 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
18516
19030
|
}
|
|
18517
19031
|
|
|
18518
19032
|
// src/daemon.ts
|
|
18519
|
-
var
|
|
19033
|
+
var import_types86 = require("@neat.is/types");
|
|
18520
19034
|
function daemonJsonPath(scanPath) {
|
|
18521
19035
|
return import_node_path67.default.join(scanPath, "neat-out", "daemon.json");
|
|
18522
19036
|
}
|
|
@@ -18641,7 +19155,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
18641
19155
|
if (!serviceName) return true;
|
|
18642
19156
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
18643
19157
|
return graph.someNode(
|
|
18644
|
-
(_id, attrs) => attrs.type ===
|
|
19158
|
+
(_id, attrs) => attrs.type === import_types86.NodeType.ServiceNode && attrs.name === serviceName
|
|
18645
19159
|
);
|
|
18646
19160
|
}
|
|
18647
19161
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
@@ -18689,6 +19203,10 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
18689
19203
|
project: entry.name,
|
|
18690
19204
|
graph,
|
|
18691
19205
|
projectDir: entry.path,
|
|
19206
|
+
// The slot's incident ledger, so an incident-emitting connector (ADR-185)
|
|
19207
|
+
// writes a build-failure incident onto the same errors.ndjson OTLP-derived
|
|
19208
|
+
// incidents land in.
|
|
19209
|
+
errorsPath: paths.errorsPath,
|
|
18692
19210
|
...neatHome3 ? { home: neatHome3 } : {},
|
|
18693
19211
|
extra: connectors,
|
|
18694
19212
|
onSkip: (skipped, reason) => console.warn(
|
|
@@ -18931,6 +19449,19 @@ async function startDaemon(opts = {}) {
|
|
|
18931
19449
|
let otlpAddress = "";
|
|
18932
19450
|
let daemonRecord = null;
|
|
18933
19451
|
if (bind) {
|
|
19452
|
+
let bareSpanIsRoutable2 = function(serviceName) {
|
|
19453
|
+
if (singleProject) {
|
|
19454
|
+
const slot = slots.get(singleProject);
|
|
19455
|
+
if (!slot) {
|
|
19456
|
+
return !serviceName || serviceNameMatchesProject(serviceName, singleProject);
|
|
19457
|
+
}
|
|
19458
|
+
return spanBelongsToSingleProject(slot.graph, singleProject, serviceName);
|
|
19459
|
+
}
|
|
19460
|
+
const entries = [...slots.values()].map((s) => s.entry);
|
|
19461
|
+
const target = routeSpanToProject(serviceName, entries);
|
|
19462
|
+
return slots.has(target) || slots.has(DEFAULT_PROJECT);
|
|
19463
|
+
};
|
|
19464
|
+
var bareSpanIsRoutable = bareSpanIsRoutable2;
|
|
18934
19465
|
const auth = readAuthEnv();
|
|
18935
19466
|
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
18936
19467
|
const restPort = resolveRestPort(opts);
|
|
@@ -19028,6 +19559,20 @@ async function startDaemon(opts = {}) {
|
|
|
19028
19559
|
const liveEntries = await listProjects().catch(() => []);
|
|
19029
19560
|
let slot = slots.get(project);
|
|
19030
19561
|
if (!slot) {
|
|
19562
|
+
if (singleProject && project === singleProject) {
|
|
19563
|
+
slot = await tryRecoverSlot({
|
|
19564
|
+
name: singleProject,
|
|
19565
|
+
path: singleProjectPath,
|
|
19566
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19567
|
+
languages: [],
|
|
19568
|
+
status: "active"
|
|
19569
|
+
});
|
|
19570
|
+
if (!slot || slot.status !== "active") {
|
|
19571
|
+
warnDroppedSpan(singleProject, slot?.errorReason ?? "unknown");
|
|
19572
|
+
return null;
|
|
19573
|
+
}
|
|
19574
|
+
return slot;
|
|
19575
|
+
}
|
|
19031
19576
|
await recordUnroutedSpan(serviceName, traceId);
|
|
19032
19577
|
return null;
|
|
19033
19578
|
}
|
|
@@ -19094,7 +19639,30 @@ async function startDaemon(opts = {}) {
|
|
|
19094
19639
|
// host, rather than accepting it and dropping the batch. `slots` covers
|
|
19095
19640
|
// active/recovering projects, `bootstrapStatus` the ones still
|
|
19096
19641
|
// extracting; a foreign or wrong-cased project name matches neither.
|
|
19097
|
-
|
|
19642
|
+
// A single-project daemon owns exactly one project by definition, so its
|
|
19643
|
+
// own name always counts as registered even before loadAll populates the
|
|
19644
|
+
// slot — otherwise a scoped span arriving during cold-start would 404 and
|
|
19645
|
+
// be lost (OTLP does not retry 4xx). resolveSlotByName then builds it (#879).
|
|
19646
|
+
isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project) || singleProject !== void 0 && project === singleProject,
|
|
19647
|
+
// #881 — the bare `/v1/traces` route replies before the span is routed
|
|
19648
|
+
// (off the queue), so tell the receiver, per batch, how many spans will
|
|
19649
|
+
// land on no project. It keeps the 200 but reports those as
|
|
19650
|
+
// partialSuccess.rejectedSpans instead of an empty partialSuccess that an
|
|
19651
|
+
// exporter reads as full acceptance. Pure — the drop + unrouted-ledger
|
|
19652
|
+
// write still happen on the async onSpan path.
|
|
19653
|
+
classifyBareRoutability: (spans) => {
|
|
19654
|
+
let rejected = 0;
|
|
19655
|
+
for (const span of spans) {
|
|
19656
|
+
if (!bareSpanIsRoutable2(span.service)) rejected++;
|
|
19657
|
+
}
|
|
19658
|
+
if (rejected === 0) return { rejected: 0 };
|
|
19659
|
+
const noun = rejected === 1 ? "span" : "spans";
|
|
19660
|
+
const verb = rejected === 1 ? "was" : "were";
|
|
19661
|
+
return {
|
|
19662
|
+
rejected,
|
|
19663
|
+
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.`
|
|
19664
|
+
};
|
|
19665
|
+
}
|
|
19098
19666
|
});
|
|
19099
19667
|
otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
|
|
19100
19668
|
console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`);
|