@neat.is/core 0.7.9 → 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-N5TPODCX.js → chunk-6T7ZHODF.js} +486 -18
- package/dist/chunk-6T7ZHODF.js.map +1 -0
- package/dist/{chunk-ILG3SMD5.js → chunk-D4OX6MUX.js} +6 -2
- package/dist/chunk-D4OX6MUX.js.map +1 -0
- package/dist/cli.cjs +1256 -252
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +689 -164
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +505 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +505 -24
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +494 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ILG3SMD5.js.map +0 -1
- package/dist/chunk-N5TPODCX.js.map +0 -1
package/dist/neatd.cjs
CHANGED
|
@@ -2229,6 +2229,7 @@ var import_yaml = require("yaml");
|
|
|
2229
2229
|
var import_types3 = require("@neat.is/types");
|
|
2230
2230
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2231
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"]);
|
|
2232
2233
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2233
2234
|
"node_modules",
|
|
2234
2235
|
".git",
|
|
@@ -2268,6 +2269,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2268
2269
|
function isConfigFile(name) {
|
|
2269
2270
|
const ext = import_node_path3.default.extname(name);
|
|
2270
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" };
|
|
2271
2273
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2272
2274
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2273
2275
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -5372,6 +5374,21 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5372
5374
|
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5373
5375
|
await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5374
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
|
+
}
|
|
5375
5392
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5376
5393
|
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
|
|
5377
5394
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
@@ -11182,8 +11199,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11182
11199
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11183
11200
|
var import_types35 = require("@neat.is/types");
|
|
11184
11201
|
init_otel();
|
|
11185
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11186
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
|
+
}
|
|
11187
11237
|
function walk7(node, visit) {
|
|
11188
11238
|
visit(node);
|
|
11189
11239
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11191,25 +11241,54 @@ function walk7(node, visit) {
|
|
|
11191
11241
|
if (child) walk7(child, visit);
|
|
11192
11242
|
}
|
|
11193
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
|
+
}
|
|
11194
11271
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11195
11272
|
if (import_node_path47.default.extname(file.path) !== ".go") return [];
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
const
|
|
11199
|
-
|
|
11200
|
-
);
|
|
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 [];
|
|
11201
11278
|
const out = [];
|
|
11202
11279
|
walk7(tree.rootNode, (node) => {
|
|
11203
11280
|
if (node.type !== "call_expression") return;
|
|
11204
11281
|
const fn = node.childForFieldName("function");
|
|
11205
11282
|
if (fn?.type !== "selector_expression") return;
|
|
11206
11283
|
const method = fn.childForFieldName("field")?.text;
|
|
11207
|
-
if (!method
|
|
11208
|
-
const
|
|
11209
|
-
if (!
|
|
11210
|
-
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;
|
|
11211
11289
|
const table = tableFromSqlStatement(sql);
|
|
11212
11290
|
if (!table) return;
|
|
11291
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11213
11292
|
const line = node.startPosition.row + 1;
|
|
11214
11293
|
out.push({
|
|
11215
11294
|
infraId: (0, import_types35.infraId)("sql-table", table),
|
|
@@ -11217,7 +11296,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11217
11296
|
kind: "sql-table",
|
|
11218
11297
|
edgeType: "CALLS",
|
|
11219
11298
|
confidenceKind: "verified-call-site",
|
|
11220
|
-
|
|
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
|
+
}
|
|
11221
11305
|
});
|
|
11222
11306
|
});
|
|
11223
11307
|
return out;
|
|
@@ -11231,12 +11315,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11231
11315
|
var import_types36 = require("@neat.is/types");
|
|
11232
11316
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11233
11317
|
var PARSE_CHUNK11 = 16384;
|
|
11234
|
-
function
|
|
11318
|
+
function makeGoParser4() {
|
|
11235
11319
|
const p = new import_tree_sitter15.default();
|
|
11236
11320
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11237
11321
|
return p;
|
|
11238
11322
|
}
|
|
11239
|
-
function
|
|
11323
|
+
function parseSource11(parser, source) {
|
|
11240
11324
|
return parser.parse(
|
|
11241
11325
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11242
11326
|
);
|
|
@@ -11660,7 +11744,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11660
11744
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11661
11745
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11662
11746
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11663
|
-
const tree =
|
|
11747
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11664
11748
|
const { structs, models, tableFor } = analyze(tree);
|
|
11665
11749
|
const out = [];
|
|
11666
11750
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -11691,7 +11775,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11691
11775
|
function gormForeignKeys(file, serviceDir) {
|
|
11692
11776
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11693
11777
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11694
|
-
const tree =
|
|
11778
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11695
11779
|
const { structs, models, tableFor } = analyze(tree);
|
|
11696
11780
|
const out = [];
|
|
11697
11781
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13486,7 +13570,7 @@ var Projects = class {
|
|
|
13486
13570
|
init_cjs_shims();
|
|
13487
13571
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
13488
13572
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
13489
|
-
var
|
|
13573
|
+
var import_types85 = require("@neat.is/types");
|
|
13490
13574
|
|
|
13491
13575
|
// src/extend/index.ts
|
|
13492
13576
|
init_cjs_shims();
|
|
@@ -14882,6 +14966,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
14882
14966
|
unresolved++;
|
|
14883
14967
|
continue;
|
|
14884
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
|
+
}
|
|
14885
14986
|
if (resolved.ensureInfraNode) {
|
|
14886
14987
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
14887
14988
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -17116,6 +17217,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
17116
17217
|
};
|
|
17117
17218
|
}
|
|
17118
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
|
+
|
|
17119
17552
|
// src/connectors/registry.ts
|
|
17120
17553
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
17121
17554
|
async function authProbe(input) {
|
|
@@ -17403,6 +17836,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17403
17836
|
...fetchImpl ? { fetchImpl } : {}
|
|
17404
17837
|
});
|
|
17405
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
|
+
}
|
|
17406
17874
|
}
|
|
17407
17875
|
};
|
|
17408
17876
|
function vercelCredsFrom(credentials) {
|
|
@@ -17584,7 +18052,11 @@ async function startConnectorPolling(input) {
|
|
|
17584
18052
|
const stopFns = all.map(
|
|
17585
18053
|
(registration) => startConnectorPollLoop(
|
|
17586
18054
|
registration.connector,
|
|
17587
|
-
{
|
|
18055
|
+
{
|
|
18056
|
+
projectDir: input.projectDir,
|
|
18057
|
+
credentials: registration.credentials,
|
|
18058
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
18059
|
+
},
|
|
17588
18060
|
input.graph,
|
|
17589
18061
|
registration.resolveTarget,
|
|
17590
18062
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -17754,11 +18226,11 @@ function registerRoutes(scope, ctx) {
|
|
|
17754
18226
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17755
18227
|
const parsed = [];
|
|
17756
18228
|
for (const c of candidates) {
|
|
17757
|
-
const r =
|
|
18229
|
+
const r = import_types85.DivergenceTypeSchema.safeParse(c);
|
|
17758
18230
|
if (!r.success) {
|
|
17759
18231
|
return reply.code(400).send({
|
|
17760
18232
|
error: `unknown divergence type "${c}"`,
|
|
17761
|
-
allowed:
|
|
18233
|
+
allowed: import_types85.DivergenceTypeSchema.options
|
|
17762
18234
|
});
|
|
17763
18235
|
}
|
|
17764
18236
|
parsed.push(r.data);
|
|
@@ -17865,10 +18337,15 @@ function registerRoutes(scope, ctx) {
|
|
|
17865
18337
|
}
|
|
17866
18338
|
const reg = built.registration;
|
|
17867
18339
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18340
|
+
const incidentsPath = errorsPathFor(proj);
|
|
17868
18341
|
try {
|
|
17869
18342
|
const result = await ctx.runPoll(
|
|
17870
18343
|
reg.connector,
|
|
17871
|
-
{
|
|
18344
|
+
{
|
|
18345
|
+
projectDir: proj.scanPath ?? "",
|
|
18346
|
+
credentials: reg.credentials,
|
|
18347
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
18348
|
+
},
|
|
17872
18349
|
proj.graph,
|
|
17873
18350
|
reg.resolveTarget
|
|
17874
18351
|
);
|
|
@@ -18067,7 +18544,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18067
18544
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
18068
18545
|
let violations = await log.readAll();
|
|
18069
18546
|
if (req2.query.severity) {
|
|
18070
|
-
const sev =
|
|
18547
|
+
const sev = import_types85.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
18071
18548
|
if (!sev.success) {
|
|
18072
18549
|
return reply.code(400).send({
|
|
18073
18550
|
error: "invalid severity",
|
|
@@ -18106,7 +18583,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18106
18583
|
scope.post("/policies/check", async (req2, reply) => {
|
|
18107
18584
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
18108
18585
|
if (!proj) return;
|
|
18109
|
-
const parsed =
|
|
18586
|
+
const parsed = import_types85.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
18110
18587
|
if (!parsed.success) {
|
|
18111
18588
|
return reply.code(400).send({
|
|
18112
18589
|
error: "invalid /policies/check body",
|
|
@@ -18447,7 +18924,7 @@ function unroutedErrorsPath(neatHome4) {
|
|
|
18447
18924
|
}
|
|
18448
18925
|
|
|
18449
18926
|
// src/daemon.ts
|
|
18450
|
-
var
|
|
18927
|
+
var import_types86 = require("@neat.is/types");
|
|
18451
18928
|
function daemonJsonPath(scanPath) {
|
|
18452
18929
|
return import_node_path67.default.join(scanPath, "neat-out", "daemon.json");
|
|
18453
18930
|
}
|
|
@@ -18586,7 +19063,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
18586
19063
|
if (!serviceName) return true;
|
|
18587
19064
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
18588
19065
|
return graph.someNode(
|
|
18589
|
-
(_id, attrs) => attrs.type ===
|
|
19066
|
+
(_id, attrs) => attrs.type === import_types86.NodeType.ServiceNode && attrs.name === serviceName
|
|
18590
19067
|
);
|
|
18591
19068
|
}
|
|
18592
19069
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
@@ -18634,6 +19111,10 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
|
18634
19111
|
project: entry2.name,
|
|
18635
19112
|
graph,
|
|
18636
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,
|
|
18637
19118
|
...neatHome4 ? { home: neatHome4 } : {},
|
|
18638
19119
|
extra: connectors,
|
|
18639
19120
|
onSkip: (skipped, reason) => console.warn(
|