@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
|
@@ -963,6 +963,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
963
963
|
import { extractedEdgeId } from "@neat.is/types";
|
|
964
964
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
965
965
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
966
|
+
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
966
967
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
967
968
|
"node_modules",
|
|
968
969
|
".git",
|
|
@@ -1002,6 +1003,7 @@ async function isPythonVenvDir(dir) {
|
|
|
1002
1003
|
function isConfigFile(name) {
|
|
1003
1004
|
const ext = path3.extname(name);
|
|
1004
1005
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
1006
|
+
if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
|
|
1005
1007
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
1006
1008
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
1007
1009
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -4127,6 +4129,21 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
4127
4129
|
await fs7.mkdir(path8.dirname(ctx.errorsPath), { recursive: true });
|
|
4128
4130
|
await fs7.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4129
4131
|
}
|
|
4132
|
+
async function appendConnectorIncident(errorsPath, input) {
|
|
4133
|
+
const ev = {
|
|
4134
|
+
id: input.id,
|
|
4135
|
+
timestamp: input.timestamp,
|
|
4136
|
+
service: input.service,
|
|
4137
|
+
traceId: input.id,
|
|
4138
|
+
spanId: input.id,
|
|
4139
|
+
errorType: input.errorType,
|
|
4140
|
+
errorMessage: input.errorMessage,
|
|
4141
|
+
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
4142
|
+
affectedNode: input.affectedNode
|
|
4143
|
+
};
|
|
4144
|
+
await fs7.mkdir(path8.dirname(errorsPath), { recursive: true });
|
|
4145
|
+
await fs7.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4146
|
+
}
|
|
4130
4147
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
4131
4148
|
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : serviceId(span.service, span.env);
|
|
4132
4149
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
@@ -10488,8 +10505,41 @@ import path45 from "path";
|
|
|
10488
10505
|
import Parser14 from "tree-sitter";
|
|
10489
10506
|
import Go3 from "tree-sitter-go";
|
|
10490
10507
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
10491
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
10492
10508
|
var PARSE_CHUNK10 = 16384;
|
|
10509
|
+
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
10510
|
+
"Query",
|
|
10511
|
+
"QueryContext",
|
|
10512
|
+
"QueryRow",
|
|
10513
|
+
"QueryRowContext",
|
|
10514
|
+
"Exec",
|
|
10515
|
+
"ExecContext",
|
|
10516
|
+
"Prepare",
|
|
10517
|
+
"PrepareContext"
|
|
10518
|
+
]);
|
|
10519
|
+
var SQLX_METHODS = /* @__PURE__ */ new Set([
|
|
10520
|
+
"Get",
|
|
10521
|
+
"Select",
|
|
10522
|
+
"Queryx",
|
|
10523
|
+
"QueryRowx",
|
|
10524
|
+
"NamedExec",
|
|
10525
|
+
"NamedQuery",
|
|
10526
|
+
"MustExec",
|
|
10527
|
+
"Preparex",
|
|
10528
|
+
"GetContext",
|
|
10529
|
+
"SelectContext"
|
|
10530
|
+
]);
|
|
10531
|
+
var DATABASE_SQL_IMPORT = "database/sql";
|
|
10532
|
+
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
10533
|
+
function makeGoParser3() {
|
|
10534
|
+
const p = new Parser14();
|
|
10535
|
+
p.setLanguage(Go3);
|
|
10536
|
+
return p;
|
|
10537
|
+
}
|
|
10538
|
+
function parseSource10(parser, source) {
|
|
10539
|
+
return parser.parse(
|
|
10540
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK10)
|
|
10541
|
+
);
|
|
10542
|
+
}
|
|
10493
10543
|
function walk7(node, visit) {
|
|
10494
10544
|
visit(node);
|
|
10495
10545
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -10497,25 +10547,54 @@ function walk7(node, visit) {
|
|
|
10497
10547
|
if (child) walk7(child, visit);
|
|
10498
10548
|
}
|
|
10499
10549
|
}
|
|
10550
|
+
function goStringLiteralValue(node) {
|
|
10551
|
+
if (!node) return null;
|
|
10552
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
10553
|
+
const t = node.text;
|
|
10554
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
10555
|
+
}
|
|
10556
|
+
return null;
|
|
10557
|
+
}
|
|
10558
|
+
function goImportsAny(root, names) {
|
|
10559
|
+
let found = false;
|
|
10560
|
+
walk7(root, (node) => {
|
|
10561
|
+
if (found || node.type !== "import_spec") return;
|
|
10562
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
10563
|
+
const value = goStringLiteralValue(node.namedChild(i));
|
|
10564
|
+
if (value !== null && names.has(value)) found = true;
|
|
10565
|
+
}
|
|
10566
|
+
});
|
|
10567
|
+
return found;
|
|
10568
|
+
}
|
|
10569
|
+
function firstStringLiteralArg(argsNode) {
|
|
10570
|
+
if (!argsNode) return null;
|
|
10571
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
10572
|
+
const value = goStringLiteralValue(argsNode.namedChild(i));
|
|
10573
|
+
if (value !== null) return value;
|
|
10574
|
+
}
|
|
10575
|
+
return null;
|
|
10576
|
+
}
|
|
10500
10577
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
10501
10578
|
if (path45.extname(file.path) !== ".go") return [];
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
const
|
|
10505
|
-
|
|
10506
|
-
);
|
|
10579
|
+
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
10580
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
10581
|
+
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
10582
|
+
const importsSqlx = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([SQLX_IMPORT]));
|
|
10583
|
+
if (!importsDatabaseSql && !importsSqlx) return [];
|
|
10507
10584
|
const out = [];
|
|
10508
10585
|
walk7(tree.rootNode, (node) => {
|
|
10509
10586
|
if (node.type !== "call_expression") return;
|
|
10510
10587
|
const fn = node.childForFieldName("function");
|
|
10511
10588
|
if (fn?.type !== "selector_expression") return;
|
|
10512
10589
|
const method = fn.childForFieldName("field")?.text;
|
|
10513
|
-
if (!method
|
|
10514
|
-
const
|
|
10515
|
-
if (!
|
|
10516
|
-
const sql =
|
|
10590
|
+
if (!method) return;
|
|
10591
|
+
const recognized = DATABASE_SQL_METHODS.has(method) || importsSqlx && SQLX_METHODS.has(method);
|
|
10592
|
+
if (!recognized) return;
|
|
10593
|
+
const sql = firstStringLiteralArg(node.childForFieldName("arguments"));
|
|
10594
|
+
if (sql === null) return;
|
|
10517
10595
|
const table = tableFromSqlStatement(sql);
|
|
10518
10596
|
if (!table) return;
|
|
10597
|
+
const columns = columnsFromSqlStatement(sql);
|
|
10519
10598
|
const line = node.startPosition.row + 1;
|
|
10520
10599
|
out.push({
|
|
10521
10600
|
infraId: infraId15("sql-table", table),
|
|
@@ -10523,7 +10602,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
10523
10602
|
kind: "sql-table",
|
|
10524
10603
|
edgeType: "CALLS",
|
|
10525
10604
|
confidenceKind: "verified-call-site",
|
|
10526
|
-
|
|
10605
|
+
...columns.length > 0 ? { columns } : {},
|
|
10606
|
+
evidence: {
|
|
10607
|
+
file: toPosix(path45.relative(serviceDir, file.path)),
|
|
10608
|
+
line,
|
|
10609
|
+
snippet: snippet(file.content, line)
|
|
10610
|
+
}
|
|
10527
10611
|
});
|
|
10528
10612
|
});
|
|
10529
10613
|
return out;
|
|
@@ -10536,12 +10620,12 @@ import Go4 from "tree-sitter-go";
|
|
|
10536
10620
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
10537
10621
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
10538
10622
|
var PARSE_CHUNK11 = 16384;
|
|
10539
|
-
function
|
|
10623
|
+
function makeGoParser4() {
|
|
10540
10624
|
const p = new Parser15();
|
|
10541
10625
|
p.setLanguage(Go4);
|
|
10542
10626
|
return p;
|
|
10543
10627
|
}
|
|
10544
|
-
function
|
|
10628
|
+
function parseSource11(parser, source) {
|
|
10545
10629
|
return parser.parse(
|
|
10546
10630
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
10547
10631
|
);
|
|
@@ -10965,7 +11049,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
10965
11049
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
10966
11050
|
if (path46.extname(file.path) !== ".go") return [];
|
|
10967
11051
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
10968
|
-
const tree =
|
|
11052
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
10969
11053
|
const { structs, models, tableFor } = analyze(tree);
|
|
10970
11054
|
const out = [];
|
|
10971
11055
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -10996,7 +11080,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
10996
11080
|
function gormForeignKeys(file, serviceDir) {
|
|
10997
11081
|
if (path46.extname(file.path) !== ".go") return [];
|
|
10998
11082
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
10999
|
-
const tree =
|
|
11083
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11000
11084
|
const { structs, models, tableFor } = analyze(tree);
|
|
11001
11085
|
const out = [];
|
|
11002
11086
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -14420,6 +14504,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
14420
14504
|
unresolved++;
|
|
14421
14505
|
continue;
|
|
14422
14506
|
}
|
|
14507
|
+
if (signal.incident) {
|
|
14508
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
14509
|
+
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
14510
|
+
unresolved++;
|
|
14511
|
+
continue;
|
|
14512
|
+
}
|
|
14513
|
+
await appendConnectorIncident(ctx.errorsPath, {
|
|
14514
|
+
id: signal.incident.id,
|
|
14515
|
+
timestamp: signal.incident.timestamp,
|
|
14516
|
+
service: signal.incident.service,
|
|
14517
|
+
errorType: signal.incident.errorType,
|
|
14518
|
+
errorMessage: signal.incident.errorMessage,
|
|
14519
|
+
...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
|
|
14520
|
+
affectedNode: resolved.targetNodeId
|
|
14521
|
+
});
|
|
14522
|
+
continue;
|
|
14523
|
+
}
|
|
14423
14524
|
if (resolved.ensureInfraNode) {
|
|
14424
14525
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
14425
14526
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -16587,6 +16688,329 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
16587
16688
|
};
|
|
16588
16689
|
}
|
|
16589
16690
|
|
|
16691
|
+
// src/connectors/eas/types.ts
|
|
16692
|
+
function readEasCredentials(raw) {
|
|
16693
|
+
const token = raw["token"];
|
|
16694
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
16695
|
+
throw new Error("eas connector: credentials.token (EXPO_TOKEN) must be a non-empty string");
|
|
16696
|
+
}
|
|
16697
|
+
return { token };
|
|
16698
|
+
}
|
|
16699
|
+
var EAS_STATUS_ERRORED = "ERRORED";
|
|
16700
|
+
var TRANSIENT_BUILD_PHASES = /* @__PURE__ */ new Set([
|
|
16701
|
+
"SPIN_UP_BUILDER",
|
|
16702
|
+
"PREPARE_CREDENTIALS",
|
|
16703
|
+
"RESTORE_CACHE",
|
|
16704
|
+
"UPLOAD_APPLICATION_ARCHIVE"
|
|
16705
|
+
]);
|
|
16706
|
+
var INTERNAL_ERROR_CODE = /INTERNAL_SERVER_ERROR|EAS_BUILD_.*INTERNAL|_INTERNAL_ERROR|UNKNOWN_ERROR/i;
|
|
16707
|
+
function isTransientFailure(err) {
|
|
16708
|
+
if (!err) return false;
|
|
16709
|
+
const phase = typeof err.buildPhase === "string" ? err.buildPhase : void 0;
|
|
16710
|
+
if (phase) {
|
|
16711
|
+
if (TRANSIENT_BUILD_PHASES.has(phase)) return true;
|
|
16712
|
+
if (/^SPIN_UP|_CREDENTIALS$|^RESTORE_CACHE/i.test(phase)) return true;
|
|
16713
|
+
}
|
|
16714
|
+
const code = typeof err.errorCode === "string" ? err.errorCode : void 0;
|
|
16715
|
+
if (code && INTERNAL_ERROR_CODE.test(code)) return true;
|
|
16716
|
+
return false;
|
|
16717
|
+
}
|
|
16718
|
+
var FIELD_SEP3 = "\0";
|
|
16719
|
+
var EAS_TARGET_KIND = "eas-build";
|
|
16720
|
+
function packEasTargetName(identity) {
|
|
16721
|
+
return [identity.serviceName, identity.phase].join(FIELD_SEP3);
|
|
16722
|
+
}
|
|
16723
|
+
function parseEasTargetName(targetName) {
|
|
16724
|
+
const sep = targetName.indexOf(FIELD_SEP3);
|
|
16725
|
+
if (sep === -1) return null;
|
|
16726
|
+
const serviceName = targetName.slice(0, sep);
|
|
16727
|
+
const phase = targetName.slice(sep + 1);
|
|
16728
|
+
if (!serviceName) return null;
|
|
16729
|
+
return { serviceName, phase };
|
|
16730
|
+
}
|
|
16731
|
+
|
|
16732
|
+
// src/connectors/eas/client.ts
|
|
16733
|
+
var DEFAULT_EAS_API_URL = "https://api.expo.dev/graphql";
|
|
16734
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
16735
|
+
var DEFAULT_MAX_PAGES = 10;
|
|
16736
|
+
var DEFAULT_MAX_LOG_BYTES = 16 * 1024;
|
|
16737
|
+
var DEFAULT_MAX_LOOKBACK_MS6 = 7 * 24 * 60 * 60 * 1e3;
|
|
16738
|
+
var BUILDS_QUERY = `
|
|
16739
|
+
query NeatEasErroredBuilds($appId: String!, $offset: Int!, $limit: Int!) {
|
|
16740
|
+
app {
|
|
16741
|
+
byId(appId: $appId) {
|
|
16742
|
+
builds(offset: $offset, limit: $limit, filter: { status: ERRORED }) {
|
|
16743
|
+
id
|
|
16744
|
+
status
|
|
16745
|
+
platform
|
|
16746
|
+
buildProfile
|
|
16747
|
+
gitCommitHash
|
|
16748
|
+
gitCommitMessage
|
|
16749
|
+
gitRef
|
|
16750
|
+
isGitWorkingTreeDirty
|
|
16751
|
+
createdAt
|
|
16752
|
+
completedAt
|
|
16753
|
+
error {
|
|
16754
|
+
buildPhase
|
|
16755
|
+
errorCode
|
|
16756
|
+
message
|
|
16757
|
+
docsUrl
|
|
16758
|
+
}
|
|
16759
|
+
logFileUrls
|
|
16760
|
+
}
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16764
|
+
`;
|
|
16765
|
+
async function easGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
16766
|
+
const res = await junctionFetch(
|
|
16767
|
+
apiUrl,
|
|
16768
|
+
{
|
|
16769
|
+
method: "POST",
|
|
16770
|
+
headers: {
|
|
16771
|
+
"Content-Type": "application/json",
|
|
16772
|
+
...bearerAuthHeader(token)
|
|
16773
|
+
},
|
|
16774
|
+
body: JSON.stringify({ query, variables })
|
|
16775
|
+
},
|
|
16776
|
+
// accountKey: the Expo app id — the per-(provider, accountKey) rate-limit
|
|
16777
|
+
// bucket (ADR-131), the closest thing this connector carries to "one account".
|
|
16778
|
+
{ provider: "eas", accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
16779
|
+
);
|
|
16780
|
+
if (!res.ok) {
|
|
16781
|
+
throw new Error(`Expo GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
16782
|
+
}
|
|
16783
|
+
const body = await res.json();
|
|
16784
|
+
if (body.errors && body.errors.length > 0) {
|
|
16785
|
+
throw new Error(`Expo GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
|
|
16786
|
+
}
|
|
16787
|
+
if (!body.data) throw new Error("Expo GraphQL response carried no data");
|
|
16788
|
+
return body.data;
|
|
16789
|
+
}
|
|
16790
|
+
async function fetchErroredBuilds(token, config, fetchImpl) {
|
|
16791
|
+
const apiUrl = config.apiUrl ?? DEFAULT_EAS_API_URL;
|
|
16792
|
+
const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
16793
|
+
const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;
|
|
16794
|
+
const out = [];
|
|
16795
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16796
|
+
for (let page = 0; page < maxPages; page++) {
|
|
16797
|
+
const data = await easGraphQL(
|
|
16798
|
+
apiUrl,
|
|
16799
|
+
token,
|
|
16800
|
+
BUILDS_QUERY,
|
|
16801
|
+
{ appId: config.appId, offset: page * pageSize, limit: pageSize },
|
|
16802
|
+
config.appId,
|
|
16803
|
+
fetchImpl
|
|
16804
|
+
);
|
|
16805
|
+
const builds = data.app?.byId?.builds;
|
|
16806
|
+
if (!Array.isArray(builds)) break;
|
|
16807
|
+
let added = 0;
|
|
16808
|
+
for (const b of builds) {
|
|
16809
|
+
if (!b || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
16810
|
+
if (b.status !== EAS_STATUS_ERRORED) continue;
|
|
16811
|
+
if (seen.has(b.id)) continue;
|
|
16812
|
+
seen.add(b.id);
|
|
16813
|
+
out.push(b);
|
|
16814
|
+
added++;
|
|
16815
|
+
}
|
|
16816
|
+
if (builds.length < pageSize) break;
|
|
16817
|
+
if (added === 0) break;
|
|
16818
|
+
}
|
|
16819
|
+
return out;
|
|
16820
|
+
}
|
|
16821
|
+
async function fetchBuildLogs(logFileUrls, maxBytes = DEFAULT_MAX_LOG_BYTES, fetchImpl) {
|
|
16822
|
+
if (!Array.isArray(logFileUrls) || logFileUrls.length === 0) return "";
|
|
16823
|
+
const doFetch = fetchImpl ?? fetch;
|
|
16824
|
+
const chunks = [];
|
|
16825
|
+
for (const url of logFileUrls) {
|
|
16826
|
+
if (typeof url !== "string" || url.length === 0) continue;
|
|
16827
|
+
try {
|
|
16828
|
+
const res = await doFetch(url);
|
|
16829
|
+
if (!res.ok) continue;
|
|
16830
|
+
chunks.push(await res.text());
|
|
16831
|
+
} catch {
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16834
|
+
const joined = chunks.join("\n");
|
|
16835
|
+
return joined.length > maxBytes ? joined.slice(joined.length - maxBytes) : joined;
|
|
16836
|
+
}
|
|
16837
|
+
|
|
16838
|
+
// src/connectors/eas/map.ts
|
|
16839
|
+
function buildEventTime(build) {
|
|
16840
|
+
if (typeof build.completedAt === "string" && build.completedAt.length > 0) return build.completedAt;
|
|
16841
|
+
if (typeof build.createdAt === "string" && build.createdAt.length > 0) return build.createdAt;
|
|
16842
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
16843
|
+
}
|
|
16844
|
+
function incidentMessage2(build) {
|
|
16845
|
+
const err = build.error ?? {};
|
|
16846
|
+
const phase = typeof err.buildPhase === "string" && err.buildPhase.length > 0 ? ` at ${err.buildPhase}` : "";
|
|
16847
|
+
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";
|
|
16848
|
+
let msg = `EAS build failed${phase}: ${detail}`;
|
|
16849
|
+
if (build.isGitWorkingTreeDirty === true) {
|
|
16850
|
+
msg += " (built from a dirty working tree \u2014 the commit may not represent what built)";
|
|
16851
|
+
}
|
|
16852
|
+
return msg;
|
|
16853
|
+
}
|
|
16854
|
+
function incidentAttributes(build) {
|
|
16855
|
+
const attrs = {};
|
|
16856
|
+
const err = build.error ?? {};
|
|
16857
|
+
const put = (k, v) => {
|
|
16858
|
+
if (typeof v === "string" && v.length === 0) return;
|
|
16859
|
+
if (v !== void 0 && v !== null) attrs[k] = v;
|
|
16860
|
+
};
|
|
16861
|
+
put("eas.buildId", build.id);
|
|
16862
|
+
put("eas.platform", build.platform ?? void 0);
|
|
16863
|
+
put("eas.buildProfile", build.buildProfile ?? void 0);
|
|
16864
|
+
put("eas.buildPhase", err.buildPhase ?? void 0);
|
|
16865
|
+
put("eas.errorCode", err.errorCode ?? void 0);
|
|
16866
|
+
put("eas.docsUrl", err.docsUrl ?? void 0);
|
|
16867
|
+
put("eas.gitCommitHash", build.gitCommitHash ?? void 0);
|
|
16868
|
+
put("eas.gitRef", build.gitRef ?? void 0);
|
|
16869
|
+
put("eas.gitCommitMessage", build.gitCommitMessage ?? void 0);
|
|
16870
|
+
if (typeof build.isGitWorkingTreeDirty === "boolean") {
|
|
16871
|
+
attrs["eas.gitWorkingTreeDirty"] = build.isGitWorkingTreeDirty;
|
|
16872
|
+
if (build.isGitWorkingTreeDirty) attrs["eas.confidence"] = "low";
|
|
16873
|
+
}
|
|
16874
|
+
put("eas.createdAt", build.createdAt ?? void 0);
|
|
16875
|
+
put("eas.completedAt", build.completedAt ?? void 0);
|
|
16876
|
+
if (typeof build.logsText === "string" && build.logsText.length > 0) {
|
|
16877
|
+
attrs["eas.logs"] = build.logsText;
|
|
16878
|
+
}
|
|
16879
|
+
return attrs;
|
|
16880
|
+
}
|
|
16881
|
+
function mapBuildToSignal(build, serviceName) {
|
|
16882
|
+
if (!build || typeof build !== "object") return null;
|
|
16883
|
+
if (build.status !== EAS_STATUS_ERRORED) return null;
|
|
16884
|
+
if (!build.error) return null;
|
|
16885
|
+
if (isTransientFailure(build.error)) return null;
|
|
16886
|
+
const timestamp = buildEventTime(build);
|
|
16887
|
+
const phase = typeof build.error.buildPhase === "string" ? build.error.buildPhase : "";
|
|
16888
|
+
return {
|
|
16889
|
+
targetKind: EAS_TARGET_KIND,
|
|
16890
|
+
targetName: packEasTargetName({ serviceName, phase }),
|
|
16891
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
16892
|
+
callCount: 0,
|
|
16893
|
+
errorCount: 0,
|
|
16894
|
+
lastObservedIso: timestamp,
|
|
16895
|
+
incident: {
|
|
16896
|
+
id: `eas:build:${build.id}`,
|
|
16897
|
+
timestamp,
|
|
16898
|
+
service: serviceName,
|
|
16899
|
+
errorType: "eas-build-failure",
|
|
16900
|
+
errorMessage: incidentMessage2(build),
|
|
16901
|
+
attributes: incidentAttributes(build)
|
|
16902
|
+
}
|
|
16903
|
+
};
|
|
16904
|
+
}
|
|
16905
|
+
function mapBuildsToSignals(builds, serviceName) {
|
|
16906
|
+
const out = [];
|
|
16907
|
+
for (const build of builds) {
|
|
16908
|
+
const signal = mapBuildToSignal(build, serviceName);
|
|
16909
|
+
if (signal) out.push(signal);
|
|
16910
|
+
}
|
|
16911
|
+
return out;
|
|
16912
|
+
}
|
|
16913
|
+
|
|
16914
|
+
// src/connectors/eas/resolve.ts
|
|
16915
|
+
import { EdgeType as EdgeType34, NodeType as NodeType32, parseFileId as parseFileId3 } from "@neat.is/types";
|
|
16916
|
+
var NO_ENV2 = "unknown";
|
|
16917
|
+
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
16918
|
+
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
16919
|
+
"READ_APP_CONFIG",
|
|
16920
|
+
"CONFIGURE_EXPO_UPDATES",
|
|
16921
|
+
"CALCULATE_EXPO_UPDATES_RUNTIME_VERSION"
|
|
16922
|
+
]);
|
|
16923
|
+
function configBasenamesForPhase(phase) {
|
|
16924
|
+
if (EAS_JSON_PHASES.has(phase)) return ["eas.json"];
|
|
16925
|
+
if (APP_CONFIG_PHASES.has(phase)) return ["app.json", "app.config.json"];
|
|
16926
|
+
return [];
|
|
16927
|
+
}
|
|
16928
|
+
function configNodeService(graph, configNodeId) {
|
|
16929
|
+
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
16930
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
16931
|
+
if (edge.type !== EdgeType34.CONFIGURED_BY) continue;
|
|
16932
|
+
const parsed = parseFileId3(edge.source);
|
|
16933
|
+
if (parsed) return parsed.service;
|
|
16934
|
+
}
|
|
16935
|
+
return null;
|
|
16936
|
+
}
|
|
16937
|
+
function findConfigNode(graph, basenames, serviceName) {
|
|
16938
|
+
let scoped = null;
|
|
16939
|
+
let anyMatch = null;
|
|
16940
|
+
graph.forEachNode((id, attrs) => {
|
|
16941
|
+
if (scoped) return;
|
|
16942
|
+
const node = attrs;
|
|
16943
|
+
if (node.type !== NodeType32.ConfigNode) return;
|
|
16944
|
+
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
16945
|
+
if (anyMatch === null) anyMatch = id;
|
|
16946
|
+
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
16947
|
+
});
|
|
16948
|
+
return scoped ?? anyMatch;
|
|
16949
|
+
}
|
|
16950
|
+
function createEasResolveTarget(graph) {
|
|
16951
|
+
return (signal) => {
|
|
16952
|
+
if (signal.targetKind !== EAS_TARGET_KIND) return null;
|
|
16953
|
+
const identity = parseEasTargetName(signal.targetName);
|
|
16954
|
+
if (!identity) return null;
|
|
16955
|
+
const { serviceName, phase } = identity;
|
|
16956
|
+
const basenames = configBasenamesForPhase(phase);
|
|
16957
|
+
if (basenames.length > 0) {
|
|
16958
|
+
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
16959
|
+
if (configNodeId) {
|
|
16960
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: EdgeType34.CALLS };
|
|
16961
|
+
}
|
|
16962
|
+
}
|
|
16963
|
+
return {
|
|
16964
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
16965
|
+
serviceName,
|
|
16966
|
+
edgeType: EdgeType34.CALLS
|
|
16967
|
+
};
|
|
16968
|
+
};
|
|
16969
|
+
}
|
|
16970
|
+
|
|
16971
|
+
// src/connectors/eas/index.ts
|
|
16972
|
+
function isBuildSince(build, sinceIso) {
|
|
16973
|
+
const t = Date.parse(buildEventTime(build));
|
|
16974
|
+
const s = Date.parse(sinceIso);
|
|
16975
|
+
if (Number.isNaN(t) || Number.isNaN(s)) return true;
|
|
16976
|
+
return t > s;
|
|
16977
|
+
}
|
|
16978
|
+
function boundedSinceIso2(since, now, maxLookbackMs) {
|
|
16979
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
16980
|
+
if (!since) return floor.toISOString();
|
|
16981
|
+
const sinceMs = new Date(since).getTime();
|
|
16982
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
16983
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
16984
|
+
}
|
|
16985
|
+
var EasConnector = class {
|
|
16986
|
+
constructor(config, fetchImpl) {
|
|
16987
|
+
this.config = config;
|
|
16988
|
+
this.fetchImpl = fetchImpl;
|
|
16989
|
+
}
|
|
16990
|
+
config;
|
|
16991
|
+
fetchImpl;
|
|
16992
|
+
provider = "eas";
|
|
16993
|
+
async poll(ctx) {
|
|
16994
|
+
const creds = readEasCredentials(ctx.credentials);
|
|
16995
|
+
const serviceName = this.config.serviceName ?? this.config.appId;
|
|
16996
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS6;
|
|
16997
|
+
const sinceIso = boundedSinceIso2(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
16998
|
+
const builds = await fetchErroredBuilds(creds.token, this.config, this.fetchImpl);
|
|
16999
|
+
const fresh = builds.filter((b) => isBuildSince(b, sinceIso));
|
|
17000
|
+
const maxLogBytes = this.config.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
17001
|
+
for (const build of fresh) {
|
|
17002
|
+
build.logsText = await fetchBuildLogs(build.logFileUrls, maxLogBytes, this.fetchImpl);
|
|
17003
|
+
}
|
|
17004
|
+
return mapBuildsToSignals(fresh, serviceName);
|
|
17005
|
+
}
|
|
17006
|
+
};
|
|
17007
|
+
function createEasConnector(graph, config, fetchImpl) {
|
|
17008
|
+
return {
|
|
17009
|
+
connector: new EasConnector(config, fetchImpl),
|
|
17010
|
+
resolveTarget: createEasResolveTarget(graph)
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
|
|
16590
17014
|
// src/connectors/registry.ts
|
|
16591
17015
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
16592
17016
|
async function authProbe(input) {
|
|
@@ -16874,6 +17298,41 @@ var PROVIDER_DISPATCH = {
|
|
|
16874
17298
|
...fetchImpl ? { fetchImpl } : {}
|
|
16875
17299
|
});
|
|
16876
17300
|
}
|
|
17301
|
+
},
|
|
17302
|
+
eas: {
|
|
17303
|
+
provider: "eas",
|
|
17304
|
+
// The secret is a single robot-user EXPO_TOKEN; a single-string credential
|
|
17305
|
+
// maps to `token`. `appId` is non-secret config (connector-config.md §7.1).
|
|
17306
|
+
primaryCredentialKey: "token",
|
|
17307
|
+
requiredCredentialFields: ["token"],
|
|
17308
|
+
requiredOptionFields: ["appId"],
|
|
17309
|
+
build(graph, options) {
|
|
17310
|
+
return createEasConnector(graph, options);
|
|
17311
|
+
},
|
|
17312
|
+
// Runs the connector's real `builds` query at limit 1 — the exact read poll()
|
|
17313
|
+
// performs, minus the pages — so the probe checks both that the EXPO_TOKEN
|
|
17314
|
+
// authenticates and that this app id is reachable, the same probe-the-real-
|
|
17315
|
+
// query discipline Railway and Cloud Run use over a trivial `{ __typename }`.
|
|
17316
|
+
// A bad or wrong-scoped token comes back as an Expo GraphQL error, which
|
|
17317
|
+
// `fetchErroredBuilds` throws on, so it fails honestly here rather than
|
|
17318
|
+
// silently at the first poll.
|
|
17319
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
17320
|
+
const cfg = options;
|
|
17321
|
+
const appId = String(cfg.appId ?? "");
|
|
17322
|
+
if (!appId) return { ok: false, reason: "eas: appId is required to validate" };
|
|
17323
|
+
const probeConfig = {
|
|
17324
|
+
appId,
|
|
17325
|
+
pageSize: 1,
|
|
17326
|
+
maxPages: 1,
|
|
17327
|
+
...cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}
|
|
17328
|
+
};
|
|
17329
|
+
try {
|
|
17330
|
+
await fetchErroredBuilds(String(credentials.token ?? ""), probeConfig, fetchImpl);
|
|
17331
|
+
return { ok: true };
|
|
17332
|
+
} catch (err) {
|
|
17333
|
+
return { ok: false, reason: `eas auth check failed: ${err.message}` };
|
|
17334
|
+
}
|
|
17335
|
+
}
|
|
16877
17336
|
}
|
|
16878
17337
|
};
|
|
16879
17338
|
function vercelCredsFrom(credentials) {
|
|
@@ -17086,7 +17545,11 @@ async function startConnectorPolling(input) {
|
|
|
17086
17545
|
const stopFns = all.map(
|
|
17087
17546
|
(registration) => startConnectorPollLoop(
|
|
17088
17547
|
registration.connector,
|
|
17089
|
-
{
|
|
17548
|
+
{
|
|
17549
|
+
projectDir: input.projectDir,
|
|
17550
|
+
credentials: registration.credentials,
|
|
17551
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
17552
|
+
},
|
|
17090
17553
|
input.graph,
|
|
17091
17554
|
registration.resolveTarget,
|
|
17092
17555
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -17415,10 +17878,15 @@ function registerRoutes(scope, ctx) {
|
|
|
17415
17878
|
}
|
|
17416
17879
|
const reg = built.registration;
|
|
17417
17880
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17881
|
+
const incidentsPath = errorsPathFor(proj);
|
|
17418
17882
|
try {
|
|
17419
17883
|
const result = await ctx.runPoll(
|
|
17420
17884
|
reg.connector,
|
|
17421
|
-
{
|
|
17885
|
+
{
|
|
17886
|
+
projectDir: proj.scanPath ?? "",
|
|
17887
|
+
credentials: reg.credentials,
|
|
17888
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
17889
|
+
},
|
|
17422
17890
|
proj.graph,
|
|
17423
17891
|
reg.resolveTarget
|
|
17424
17892
|
);
|
|
@@ -18056,4 +18524,4 @@ export {
|
|
|
18056
18524
|
deprovisionConnector,
|
|
18057
18525
|
buildApi
|
|
18058
18526
|
};
|
|
18059
|
-
//# sourceMappingURL=chunk-
|
|
18527
|
+
//# sourceMappingURL=chunk-6T7ZHODF.js.map
|