@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.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
reconcileDaemonRecordSync,
|
|
4
4
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-D4OX6MUX.js";
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
registryPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6T7ZHODF.js";
|
|
10
10
|
import {
|
|
11
11
|
BindAuthorityError,
|
|
12
12
|
__require
|
package/dist/server.cjs
CHANGED
|
@@ -755,7 +755,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
755
755
|
init_cjs_shims();
|
|
756
756
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
757
757
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
758
|
-
var
|
|
758
|
+
var import_types85 = require("@neat.is/types");
|
|
759
759
|
|
|
760
760
|
// src/extend/index.ts
|
|
761
761
|
init_cjs_shims();
|
|
@@ -2017,6 +2017,7 @@ var import_yaml = require("yaml");
|
|
|
2017
2017
|
var import_types3 = require("@neat.is/types");
|
|
2018
2018
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2019
2019
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2020
|
+
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
2020
2021
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2021
2022
|
"node_modules",
|
|
2022
2023
|
".git",
|
|
@@ -2056,6 +2057,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2056
2057
|
function isConfigFile(name) {
|
|
2057
2058
|
const ext = import_node_path5.default.extname(name);
|
|
2058
2059
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2060
|
+
if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
|
|
2059
2061
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2060
2062
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2061
2063
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -5109,6 +5111,21 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5109
5111
|
await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5110
5112
|
await import_node_fs9.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5111
5113
|
}
|
|
5114
|
+
async function appendConnectorIncident(errorsPath, input) {
|
|
5115
|
+
const ev = {
|
|
5116
|
+
id: input.id,
|
|
5117
|
+
timestamp: input.timestamp,
|
|
5118
|
+
service: input.service,
|
|
5119
|
+
traceId: input.id,
|
|
5120
|
+
spanId: input.id,
|
|
5121
|
+
errorType: input.errorType,
|
|
5122
|
+
errorMessage: input.errorMessage,
|
|
5123
|
+
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
5124
|
+
affectedNode: input.affectedNode
|
|
5125
|
+
};
|
|
5126
|
+
await import_node_fs9.promises.mkdir(import_node_path10.default.dirname(errorsPath), { recursive: true });
|
|
5127
|
+
await import_node_fs9.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5128
|
+
}
|
|
5112
5129
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5113
5130
|
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types7.serviceId)(span.service, span.env);
|
|
5114
5131
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
@@ -11801,8 +11818,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11801
11818
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11802
11819
|
var import_types36 = require("@neat.is/types");
|
|
11803
11820
|
init_otel();
|
|
11804
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11805
11821
|
var PARSE_CHUNK10 = 16384;
|
|
11822
|
+
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
11823
|
+
"Query",
|
|
11824
|
+
"QueryContext",
|
|
11825
|
+
"QueryRow",
|
|
11826
|
+
"QueryRowContext",
|
|
11827
|
+
"Exec",
|
|
11828
|
+
"ExecContext",
|
|
11829
|
+
"Prepare",
|
|
11830
|
+
"PrepareContext"
|
|
11831
|
+
]);
|
|
11832
|
+
var SQLX_METHODS = /* @__PURE__ */ new Set([
|
|
11833
|
+
"Get",
|
|
11834
|
+
"Select",
|
|
11835
|
+
"Queryx",
|
|
11836
|
+
"QueryRowx",
|
|
11837
|
+
"NamedExec",
|
|
11838
|
+
"NamedQuery",
|
|
11839
|
+
"MustExec",
|
|
11840
|
+
"Preparex",
|
|
11841
|
+
"GetContext",
|
|
11842
|
+
"SelectContext"
|
|
11843
|
+
]);
|
|
11844
|
+
var DATABASE_SQL_IMPORT = "database/sql";
|
|
11845
|
+
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
11846
|
+
function makeGoParser3() {
|
|
11847
|
+
const p = new import_tree_sitter14.default();
|
|
11848
|
+
p.setLanguage(import_tree_sitter_go3.default);
|
|
11849
|
+
return p;
|
|
11850
|
+
}
|
|
11851
|
+
function parseSource10(parser, source) {
|
|
11852
|
+
return parser.parse(
|
|
11853
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK10)
|
|
11854
|
+
);
|
|
11855
|
+
}
|
|
11806
11856
|
function walk7(node, visit) {
|
|
11807
11857
|
visit(node);
|
|
11808
11858
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11810,25 +11860,54 @@ function walk7(node, visit) {
|
|
|
11810
11860
|
if (child) walk7(child, visit);
|
|
11811
11861
|
}
|
|
11812
11862
|
}
|
|
11863
|
+
function goStringLiteralValue(node) {
|
|
11864
|
+
if (!node) return null;
|
|
11865
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
11866
|
+
const t = node.text;
|
|
11867
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
11868
|
+
}
|
|
11869
|
+
return null;
|
|
11870
|
+
}
|
|
11871
|
+
function goImportsAny(root, names) {
|
|
11872
|
+
let found = false;
|
|
11873
|
+
walk7(root, (node) => {
|
|
11874
|
+
if (found || node.type !== "import_spec") return;
|
|
11875
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
11876
|
+
const value = goStringLiteralValue(node.namedChild(i));
|
|
11877
|
+
if (value !== null && names.has(value)) found = true;
|
|
11878
|
+
}
|
|
11879
|
+
});
|
|
11880
|
+
return found;
|
|
11881
|
+
}
|
|
11882
|
+
function firstStringLiteralArg(argsNode) {
|
|
11883
|
+
if (!argsNode) return null;
|
|
11884
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
11885
|
+
const value = goStringLiteralValue(argsNode.namedChild(i));
|
|
11886
|
+
if (value !== null) return value;
|
|
11887
|
+
}
|
|
11888
|
+
return null;
|
|
11889
|
+
}
|
|
11813
11890
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11814
11891
|
if (import_node_path49.default.extname(file.path) !== ".go") return [];
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
const
|
|
11818
|
-
|
|
11819
|
-
);
|
|
11892
|
+
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11893
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11894
|
+
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
11895
|
+
const importsSqlx = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([SQLX_IMPORT]));
|
|
11896
|
+
if (!importsDatabaseSql && !importsSqlx) return [];
|
|
11820
11897
|
const out = [];
|
|
11821
11898
|
walk7(tree.rootNode, (node) => {
|
|
11822
11899
|
if (node.type !== "call_expression") return;
|
|
11823
11900
|
const fn = node.childForFieldName("function");
|
|
11824
11901
|
if (fn?.type !== "selector_expression") return;
|
|
11825
11902
|
const method = fn.childForFieldName("field")?.text;
|
|
11826
|
-
if (!method
|
|
11827
|
-
const
|
|
11828
|
-
if (!
|
|
11829
|
-
const sql =
|
|
11903
|
+
if (!method) return;
|
|
11904
|
+
const recognized = DATABASE_SQL_METHODS.has(method) || importsSqlx && SQLX_METHODS.has(method);
|
|
11905
|
+
if (!recognized) return;
|
|
11906
|
+
const sql = firstStringLiteralArg(node.childForFieldName("arguments"));
|
|
11907
|
+
if (sql === null) return;
|
|
11830
11908
|
const table = tableFromSqlStatement(sql);
|
|
11831
11909
|
if (!table) return;
|
|
11910
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11832
11911
|
const line = node.startPosition.row + 1;
|
|
11833
11912
|
out.push({
|
|
11834
11913
|
infraId: (0, import_types36.infraId)("sql-table", table),
|
|
@@ -11836,7 +11915,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11836
11915
|
kind: "sql-table",
|
|
11837
11916
|
edgeType: "CALLS",
|
|
11838
11917
|
confidenceKind: "verified-call-site",
|
|
11839
|
-
|
|
11918
|
+
...columns.length > 0 ? { columns } : {},
|
|
11919
|
+
evidence: {
|
|
11920
|
+
file: toPosix(import_node_path49.default.relative(serviceDir, file.path)),
|
|
11921
|
+
line,
|
|
11922
|
+
snippet: snippet(file.content, line)
|
|
11923
|
+
}
|
|
11840
11924
|
});
|
|
11841
11925
|
});
|
|
11842
11926
|
return out;
|
|
@@ -11850,12 +11934,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11850
11934
|
var import_types37 = require("@neat.is/types");
|
|
11851
11935
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11852
11936
|
var PARSE_CHUNK11 = 16384;
|
|
11853
|
-
function
|
|
11937
|
+
function makeGoParser4() {
|
|
11854
11938
|
const p = new import_tree_sitter15.default();
|
|
11855
11939
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11856
11940
|
return p;
|
|
11857
11941
|
}
|
|
11858
|
-
function
|
|
11942
|
+
function parseSource11(parser, source) {
|
|
11859
11943
|
return parser.parse(
|
|
11860
11944
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11861
11945
|
);
|
|
@@ -12279,7 +12363,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
12279
12363
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
12280
12364
|
if (import_node_path50.default.extname(file.path) !== ".go") return [];
|
|
12281
12365
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12282
|
-
const tree =
|
|
12366
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12283
12367
|
const { structs, models, tableFor } = analyze(tree);
|
|
12284
12368
|
const out = [];
|
|
12285
12369
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -12310,7 +12394,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12310
12394
|
function gormForeignKeys(file, serviceDir) {
|
|
12311
12395
|
if (import_node_path50.default.extname(file.path) !== ".go") return [];
|
|
12312
12396
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12313
|
-
const tree =
|
|
12397
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12314
12398
|
const { structs, models, tableFor } = analyze(tree);
|
|
12315
12399
|
const out = [];
|
|
12316
12400
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -14593,6 +14677,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
14593
14677
|
unresolved++;
|
|
14594
14678
|
continue;
|
|
14595
14679
|
}
|
|
14680
|
+
if (signal.incident) {
|
|
14681
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
14682
|
+
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
14683
|
+
unresolved++;
|
|
14684
|
+
continue;
|
|
14685
|
+
}
|
|
14686
|
+
await appendConnectorIncident(ctx.errorsPath, {
|
|
14687
|
+
id: signal.incident.id,
|
|
14688
|
+
timestamp: signal.incident.timestamp,
|
|
14689
|
+
service: signal.incident.service,
|
|
14690
|
+
errorType: signal.incident.errorType,
|
|
14691
|
+
errorMessage: signal.incident.errorMessage,
|
|
14692
|
+
...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
|
|
14693
|
+
affectedNode: resolved.targetNodeId
|
|
14694
|
+
});
|
|
14695
|
+
continue;
|
|
14696
|
+
}
|
|
14596
14697
|
if (resolved.ensureInfraNode) {
|
|
14597
14698
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
14598
14699
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -16786,6 +16887,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
16786
16887
|
};
|
|
16787
16888
|
}
|
|
16788
16889
|
|
|
16890
|
+
// src/connectors/eas/index.ts
|
|
16891
|
+
init_cjs_shims();
|
|
16892
|
+
|
|
16893
|
+
// src/connectors/eas/client.ts
|
|
16894
|
+
init_cjs_shims();
|
|
16895
|
+
|
|
16896
|
+
// src/connectors/eas/types.ts
|
|
16897
|
+
init_cjs_shims();
|
|
16898
|
+
function readEasCredentials(raw) {
|
|
16899
|
+
const token = raw["token"];
|
|
16900
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
16901
|
+
throw new Error("eas connector: credentials.token (EXPO_TOKEN) must be a non-empty string");
|
|
16902
|
+
}
|
|
16903
|
+
return { token };
|
|
16904
|
+
}
|
|
16905
|
+
var EAS_STATUS_ERRORED = "ERRORED";
|
|
16906
|
+
var TRANSIENT_BUILD_PHASES = /* @__PURE__ */ new Set([
|
|
16907
|
+
"SPIN_UP_BUILDER",
|
|
16908
|
+
"PREPARE_CREDENTIALS",
|
|
16909
|
+
"RESTORE_CACHE",
|
|
16910
|
+
"UPLOAD_APPLICATION_ARCHIVE"
|
|
16911
|
+
]);
|
|
16912
|
+
var INTERNAL_ERROR_CODE = /INTERNAL_SERVER_ERROR|EAS_BUILD_.*INTERNAL|_INTERNAL_ERROR|UNKNOWN_ERROR/i;
|
|
16913
|
+
function isTransientFailure(err) {
|
|
16914
|
+
if (!err) return false;
|
|
16915
|
+
const phase = typeof err.buildPhase === "string" ? err.buildPhase : void 0;
|
|
16916
|
+
if (phase) {
|
|
16917
|
+
if (TRANSIENT_BUILD_PHASES.has(phase)) return true;
|
|
16918
|
+
if (/^SPIN_UP|_CREDENTIALS$|^RESTORE_CACHE/i.test(phase)) return true;
|
|
16919
|
+
}
|
|
16920
|
+
const code = typeof err.errorCode === "string" ? err.errorCode : void 0;
|
|
16921
|
+
if (code && INTERNAL_ERROR_CODE.test(code)) return true;
|
|
16922
|
+
return false;
|
|
16923
|
+
}
|
|
16924
|
+
var FIELD_SEP3 = "\0";
|
|
16925
|
+
var EAS_TARGET_KIND = "eas-build";
|
|
16926
|
+
function packEasTargetName(identity) {
|
|
16927
|
+
return [identity.serviceName, identity.phase].join(FIELD_SEP3);
|
|
16928
|
+
}
|
|
16929
|
+
function parseEasTargetName(targetName) {
|
|
16930
|
+
const sep = targetName.indexOf(FIELD_SEP3);
|
|
16931
|
+
if (sep === -1) return null;
|
|
16932
|
+
const serviceName = targetName.slice(0, sep);
|
|
16933
|
+
const phase = targetName.slice(sep + 1);
|
|
16934
|
+
if (!serviceName) return null;
|
|
16935
|
+
return { serviceName, phase };
|
|
16936
|
+
}
|
|
16937
|
+
|
|
16938
|
+
// src/connectors/eas/client.ts
|
|
16939
|
+
var DEFAULT_EAS_API_URL = "https://api.expo.dev/graphql";
|
|
16940
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
16941
|
+
var DEFAULT_MAX_PAGES = 10;
|
|
16942
|
+
var DEFAULT_MAX_LOG_BYTES = 16 * 1024;
|
|
16943
|
+
var DEFAULT_MAX_LOOKBACK_MS6 = 7 * 24 * 60 * 60 * 1e3;
|
|
16944
|
+
var BUILDS_QUERY = `
|
|
16945
|
+
query NeatEasErroredBuilds($appId: String!, $offset: Int!, $limit: Int!) {
|
|
16946
|
+
app {
|
|
16947
|
+
byId(appId: $appId) {
|
|
16948
|
+
builds(offset: $offset, limit: $limit, filter: { status: ERRORED }) {
|
|
16949
|
+
id
|
|
16950
|
+
status
|
|
16951
|
+
platform
|
|
16952
|
+
buildProfile
|
|
16953
|
+
gitCommitHash
|
|
16954
|
+
gitCommitMessage
|
|
16955
|
+
gitRef
|
|
16956
|
+
isGitWorkingTreeDirty
|
|
16957
|
+
createdAt
|
|
16958
|
+
completedAt
|
|
16959
|
+
error {
|
|
16960
|
+
buildPhase
|
|
16961
|
+
errorCode
|
|
16962
|
+
message
|
|
16963
|
+
docsUrl
|
|
16964
|
+
}
|
|
16965
|
+
logFileUrls
|
|
16966
|
+
}
|
|
16967
|
+
}
|
|
16968
|
+
}
|
|
16969
|
+
}
|
|
16970
|
+
`;
|
|
16971
|
+
async function easGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
16972
|
+
const res = await junctionFetch(
|
|
16973
|
+
apiUrl,
|
|
16974
|
+
{
|
|
16975
|
+
method: "POST",
|
|
16976
|
+
headers: {
|
|
16977
|
+
"Content-Type": "application/json",
|
|
16978
|
+
...bearerAuthHeader(token)
|
|
16979
|
+
},
|
|
16980
|
+
body: JSON.stringify({ query, variables })
|
|
16981
|
+
},
|
|
16982
|
+
// accountKey: the Expo app id — the per-(provider, accountKey) rate-limit
|
|
16983
|
+
// bucket (ADR-131), the closest thing this connector carries to "one account".
|
|
16984
|
+
{ provider: "eas", accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
16985
|
+
);
|
|
16986
|
+
if (!res.ok) {
|
|
16987
|
+
throw new Error(`Expo GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
16988
|
+
}
|
|
16989
|
+
const body = await res.json();
|
|
16990
|
+
if (body.errors && body.errors.length > 0) {
|
|
16991
|
+
throw new Error(`Expo GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
|
|
16992
|
+
}
|
|
16993
|
+
if (!body.data) throw new Error("Expo GraphQL response carried no data");
|
|
16994
|
+
return body.data;
|
|
16995
|
+
}
|
|
16996
|
+
async function fetchErroredBuilds(token, config, fetchImpl) {
|
|
16997
|
+
const apiUrl = config.apiUrl ?? DEFAULT_EAS_API_URL;
|
|
16998
|
+
const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
16999
|
+
const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;
|
|
17000
|
+
const out = [];
|
|
17001
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17002
|
+
for (let page = 0; page < maxPages; page++) {
|
|
17003
|
+
const data = await easGraphQL(
|
|
17004
|
+
apiUrl,
|
|
17005
|
+
token,
|
|
17006
|
+
BUILDS_QUERY,
|
|
17007
|
+
{ appId: config.appId, offset: page * pageSize, limit: pageSize },
|
|
17008
|
+
config.appId,
|
|
17009
|
+
fetchImpl
|
|
17010
|
+
);
|
|
17011
|
+
const builds = data.app?.byId?.builds;
|
|
17012
|
+
if (!Array.isArray(builds)) break;
|
|
17013
|
+
let added = 0;
|
|
17014
|
+
for (const b of builds) {
|
|
17015
|
+
if (!b || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
17016
|
+
if (b.status !== EAS_STATUS_ERRORED) continue;
|
|
17017
|
+
if (seen.has(b.id)) continue;
|
|
17018
|
+
seen.add(b.id);
|
|
17019
|
+
out.push(b);
|
|
17020
|
+
added++;
|
|
17021
|
+
}
|
|
17022
|
+
if (builds.length < pageSize) break;
|
|
17023
|
+
if (added === 0) break;
|
|
17024
|
+
}
|
|
17025
|
+
return out;
|
|
17026
|
+
}
|
|
17027
|
+
async function fetchBuildLogs(logFileUrls, maxBytes = DEFAULT_MAX_LOG_BYTES, fetchImpl) {
|
|
17028
|
+
if (!Array.isArray(logFileUrls) || logFileUrls.length === 0) return "";
|
|
17029
|
+
const doFetch = fetchImpl ?? fetch;
|
|
17030
|
+
const chunks = [];
|
|
17031
|
+
for (const url of logFileUrls) {
|
|
17032
|
+
if (typeof url !== "string" || url.length === 0) continue;
|
|
17033
|
+
try {
|
|
17034
|
+
const res = await doFetch(url);
|
|
17035
|
+
if (!res.ok) continue;
|
|
17036
|
+
chunks.push(await res.text());
|
|
17037
|
+
} catch {
|
|
17038
|
+
}
|
|
17039
|
+
}
|
|
17040
|
+
const joined = chunks.join("\n");
|
|
17041
|
+
return joined.length > maxBytes ? joined.slice(joined.length - maxBytes) : joined;
|
|
17042
|
+
}
|
|
17043
|
+
|
|
17044
|
+
// src/connectors/eas/map.ts
|
|
17045
|
+
init_cjs_shims();
|
|
17046
|
+
function buildEventTime(build) {
|
|
17047
|
+
if (typeof build.completedAt === "string" && build.completedAt.length > 0) return build.completedAt;
|
|
17048
|
+
if (typeof build.createdAt === "string" && build.createdAt.length > 0) return build.createdAt;
|
|
17049
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17050
|
+
}
|
|
17051
|
+
function incidentMessage2(build) {
|
|
17052
|
+
const err = build.error ?? {};
|
|
17053
|
+
const phase = typeof err.buildPhase === "string" && err.buildPhase.length > 0 ? ` at ${err.buildPhase}` : "";
|
|
17054
|
+
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";
|
|
17055
|
+
let msg = `EAS build failed${phase}: ${detail}`;
|
|
17056
|
+
if (build.isGitWorkingTreeDirty === true) {
|
|
17057
|
+
msg += " (built from a dirty working tree \u2014 the commit may not represent what built)";
|
|
17058
|
+
}
|
|
17059
|
+
return msg;
|
|
17060
|
+
}
|
|
17061
|
+
function incidentAttributes(build) {
|
|
17062
|
+
const attrs = {};
|
|
17063
|
+
const err = build.error ?? {};
|
|
17064
|
+
const put = (k, v) => {
|
|
17065
|
+
if (typeof v === "string" && v.length === 0) return;
|
|
17066
|
+
if (v !== void 0 && v !== null) attrs[k] = v;
|
|
17067
|
+
};
|
|
17068
|
+
put("eas.buildId", build.id);
|
|
17069
|
+
put("eas.platform", build.platform ?? void 0);
|
|
17070
|
+
put("eas.buildProfile", build.buildProfile ?? void 0);
|
|
17071
|
+
put("eas.buildPhase", err.buildPhase ?? void 0);
|
|
17072
|
+
put("eas.errorCode", err.errorCode ?? void 0);
|
|
17073
|
+
put("eas.docsUrl", err.docsUrl ?? void 0);
|
|
17074
|
+
put("eas.gitCommitHash", build.gitCommitHash ?? void 0);
|
|
17075
|
+
put("eas.gitRef", build.gitRef ?? void 0);
|
|
17076
|
+
put("eas.gitCommitMessage", build.gitCommitMessage ?? void 0);
|
|
17077
|
+
if (typeof build.isGitWorkingTreeDirty === "boolean") {
|
|
17078
|
+
attrs["eas.gitWorkingTreeDirty"] = build.isGitWorkingTreeDirty;
|
|
17079
|
+
if (build.isGitWorkingTreeDirty) attrs["eas.confidence"] = "low";
|
|
17080
|
+
}
|
|
17081
|
+
put("eas.createdAt", build.createdAt ?? void 0);
|
|
17082
|
+
put("eas.completedAt", build.completedAt ?? void 0);
|
|
17083
|
+
if (typeof build.logsText === "string" && build.logsText.length > 0) {
|
|
17084
|
+
attrs["eas.logs"] = build.logsText;
|
|
17085
|
+
}
|
|
17086
|
+
return attrs;
|
|
17087
|
+
}
|
|
17088
|
+
function mapBuildToSignal(build, serviceName) {
|
|
17089
|
+
if (!build || typeof build !== "object") return null;
|
|
17090
|
+
if (build.status !== EAS_STATUS_ERRORED) return null;
|
|
17091
|
+
if (!build.error) return null;
|
|
17092
|
+
if (isTransientFailure(build.error)) return null;
|
|
17093
|
+
const timestamp = buildEventTime(build);
|
|
17094
|
+
const phase = typeof build.error.buildPhase === "string" ? build.error.buildPhase : "";
|
|
17095
|
+
return {
|
|
17096
|
+
targetKind: EAS_TARGET_KIND,
|
|
17097
|
+
targetName: packEasTargetName({ serviceName, phase }),
|
|
17098
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
17099
|
+
callCount: 0,
|
|
17100
|
+
errorCount: 0,
|
|
17101
|
+
lastObservedIso: timestamp,
|
|
17102
|
+
incident: {
|
|
17103
|
+
id: `eas:build:${build.id}`,
|
|
17104
|
+
timestamp,
|
|
17105
|
+
service: serviceName,
|
|
17106
|
+
errorType: "eas-build-failure",
|
|
17107
|
+
errorMessage: incidentMessage2(build),
|
|
17108
|
+
attributes: incidentAttributes(build)
|
|
17109
|
+
}
|
|
17110
|
+
};
|
|
17111
|
+
}
|
|
17112
|
+
function mapBuildsToSignals(builds, serviceName) {
|
|
17113
|
+
const out = [];
|
|
17114
|
+
for (const build of builds) {
|
|
17115
|
+
const signal = mapBuildToSignal(build, serviceName);
|
|
17116
|
+
if (signal) out.push(signal);
|
|
17117
|
+
}
|
|
17118
|
+
return out;
|
|
17119
|
+
}
|
|
17120
|
+
|
|
17121
|
+
// src/connectors/eas/resolve.ts
|
|
17122
|
+
init_cjs_shims();
|
|
17123
|
+
var import_types82 = require("@neat.is/types");
|
|
17124
|
+
var NO_ENV2 = "unknown";
|
|
17125
|
+
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17126
|
+
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
17127
|
+
"READ_APP_CONFIG",
|
|
17128
|
+
"CONFIGURE_EXPO_UPDATES",
|
|
17129
|
+
"CALCULATE_EXPO_UPDATES_RUNTIME_VERSION"
|
|
17130
|
+
]);
|
|
17131
|
+
function configBasenamesForPhase(phase) {
|
|
17132
|
+
if (EAS_JSON_PHASES.has(phase)) return ["eas.json"];
|
|
17133
|
+
if (APP_CONFIG_PHASES.has(phase)) return ["app.json", "app.config.json"];
|
|
17134
|
+
return [];
|
|
17135
|
+
}
|
|
17136
|
+
function configNodeService(graph, configNodeId) {
|
|
17137
|
+
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
17138
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
17139
|
+
if (edge.type !== import_types82.EdgeType.CONFIGURED_BY) continue;
|
|
17140
|
+
const parsed = (0, import_types82.parseFileId)(edge.source);
|
|
17141
|
+
if (parsed) return parsed.service;
|
|
17142
|
+
}
|
|
17143
|
+
return null;
|
|
17144
|
+
}
|
|
17145
|
+
function findConfigNode(graph, basenames, serviceName) {
|
|
17146
|
+
let scoped = null;
|
|
17147
|
+
let anyMatch = null;
|
|
17148
|
+
graph.forEachNode((id, attrs) => {
|
|
17149
|
+
if (scoped) return;
|
|
17150
|
+
const node = attrs;
|
|
17151
|
+
if (node.type !== import_types82.NodeType.ConfigNode) return;
|
|
17152
|
+
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17153
|
+
if (anyMatch === null) anyMatch = id;
|
|
17154
|
+
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
17155
|
+
});
|
|
17156
|
+
return scoped ?? anyMatch;
|
|
17157
|
+
}
|
|
17158
|
+
function createEasResolveTarget(graph) {
|
|
17159
|
+
return (signal) => {
|
|
17160
|
+
if (signal.targetKind !== EAS_TARGET_KIND) return null;
|
|
17161
|
+
const identity = parseEasTargetName(signal.targetName);
|
|
17162
|
+
if (!identity) return null;
|
|
17163
|
+
const { serviceName, phase } = identity;
|
|
17164
|
+
const basenames = configBasenamesForPhase(phase);
|
|
17165
|
+
if (basenames.length > 0) {
|
|
17166
|
+
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
17167
|
+
if (configNodeId) {
|
|
17168
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types82.EdgeType.CALLS };
|
|
17169
|
+
}
|
|
17170
|
+
}
|
|
17171
|
+
return {
|
|
17172
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
17173
|
+
serviceName,
|
|
17174
|
+
edgeType: import_types82.EdgeType.CALLS
|
|
17175
|
+
};
|
|
17176
|
+
};
|
|
17177
|
+
}
|
|
17178
|
+
|
|
17179
|
+
// src/connectors/eas/index.ts
|
|
17180
|
+
function isBuildSince(build, sinceIso) {
|
|
17181
|
+
const t = Date.parse(buildEventTime(build));
|
|
17182
|
+
const s = Date.parse(sinceIso);
|
|
17183
|
+
if (Number.isNaN(t) || Number.isNaN(s)) return true;
|
|
17184
|
+
return t > s;
|
|
17185
|
+
}
|
|
17186
|
+
function boundedSinceIso2(since, now, maxLookbackMs) {
|
|
17187
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
17188
|
+
if (!since) return floor.toISOString();
|
|
17189
|
+
const sinceMs = new Date(since).getTime();
|
|
17190
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
17191
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
17192
|
+
}
|
|
17193
|
+
var EasConnector = class {
|
|
17194
|
+
constructor(config, fetchImpl) {
|
|
17195
|
+
this.config = config;
|
|
17196
|
+
this.fetchImpl = fetchImpl;
|
|
17197
|
+
}
|
|
17198
|
+
config;
|
|
17199
|
+
fetchImpl;
|
|
17200
|
+
provider = "eas";
|
|
17201
|
+
async poll(ctx) {
|
|
17202
|
+
const creds = readEasCredentials(ctx.credentials);
|
|
17203
|
+
const serviceName = this.config.serviceName ?? this.config.appId;
|
|
17204
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS6;
|
|
17205
|
+
const sinceIso = boundedSinceIso2(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
17206
|
+
const builds = await fetchErroredBuilds(creds.token, this.config, this.fetchImpl);
|
|
17207
|
+
const fresh = builds.filter((b) => isBuildSince(b, sinceIso));
|
|
17208
|
+
const maxLogBytes = this.config.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
17209
|
+
for (const build of fresh) {
|
|
17210
|
+
build.logsText = await fetchBuildLogs(build.logFileUrls, maxLogBytes, this.fetchImpl);
|
|
17211
|
+
}
|
|
17212
|
+
return mapBuildsToSignals(fresh, serviceName);
|
|
17213
|
+
}
|
|
17214
|
+
};
|
|
17215
|
+
function createEasConnector(graph, config, fetchImpl) {
|
|
17216
|
+
return {
|
|
17217
|
+
connector: new EasConnector(config, fetchImpl),
|
|
17218
|
+
resolveTarget: createEasResolveTarget(graph)
|
|
17219
|
+
};
|
|
17220
|
+
}
|
|
17221
|
+
|
|
16789
17222
|
// src/connectors/registry.ts
|
|
16790
17223
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
16791
17224
|
async function authProbe(input) {
|
|
@@ -17073,6 +17506,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17073
17506
|
...fetchImpl ? { fetchImpl } : {}
|
|
17074
17507
|
});
|
|
17075
17508
|
}
|
|
17509
|
+
},
|
|
17510
|
+
eas: {
|
|
17511
|
+
provider: "eas",
|
|
17512
|
+
// The secret is a single robot-user EXPO_TOKEN; a single-string credential
|
|
17513
|
+
// maps to `token`. `appId` is non-secret config (connector-config.md §7.1).
|
|
17514
|
+
primaryCredentialKey: "token",
|
|
17515
|
+
requiredCredentialFields: ["token"],
|
|
17516
|
+
requiredOptionFields: ["appId"],
|
|
17517
|
+
build(graph, options) {
|
|
17518
|
+
return createEasConnector(graph, options);
|
|
17519
|
+
},
|
|
17520
|
+
// Runs the connector's real `builds` query at limit 1 — the exact read poll()
|
|
17521
|
+
// performs, minus the pages — so the probe checks both that the EXPO_TOKEN
|
|
17522
|
+
// authenticates and that this app id is reachable, the same probe-the-real-
|
|
17523
|
+
// query discipline Railway and Cloud Run use over a trivial `{ __typename }`.
|
|
17524
|
+
// A bad or wrong-scoped token comes back as an Expo GraphQL error, which
|
|
17525
|
+
// `fetchErroredBuilds` throws on, so it fails honestly here rather than
|
|
17526
|
+
// silently at the first poll.
|
|
17527
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
17528
|
+
const cfg = options;
|
|
17529
|
+
const appId = String(cfg.appId ?? "");
|
|
17530
|
+
if (!appId) return { ok: false, reason: "eas: appId is required to validate" };
|
|
17531
|
+
const probeConfig = {
|
|
17532
|
+
appId,
|
|
17533
|
+
pageSize: 1,
|
|
17534
|
+
maxPages: 1,
|
|
17535
|
+
...cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}
|
|
17536
|
+
};
|
|
17537
|
+
try {
|
|
17538
|
+
await fetchErroredBuilds(String(credentials.token ?? ""), probeConfig, fetchImpl);
|
|
17539
|
+
return { ok: true };
|
|
17540
|
+
} catch (err) {
|
|
17541
|
+
return { ok: false, reason: `eas auth check failed: ${err.message}` };
|
|
17542
|
+
}
|
|
17543
|
+
}
|
|
17076
17544
|
}
|
|
17077
17545
|
};
|
|
17078
17546
|
function vercelCredsFrom(credentials) {
|
|
@@ -17382,11 +17850,11 @@ function registerRoutes(scope, ctx) {
|
|
|
17382
17850
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17383
17851
|
const parsed = [];
|
|
17384
17852
|
for (const c of candidates) {
|
|
17385
|
-
const r =
|
|
17853
|
+
const r = import_types85.DivergenceTypeSchema.safeParse(c);
|
|
17386
17854
|
if (!r.success) {
|
|
17387
17855
|
return reply.code(400).send({
|
|
17388
17856
|
error: `unknown divergence type "${c}"`,
|
|
17389
|
-
allowed:
|
|
17857
|
+
allowed: import_types85.DivergenceTypeSchema.options
|
|
17390
17858
|
});
|
|
17391
17859
|
}
|
|
17392
17860
|
parsed.push(r.data);
|
|
@@ -17493,10 +17961,15 @@ function registerRoutes(scope, ctx) {
|
|
|
17493
17961
|
}
|
|
17494
17962
|
const reg = built.registration;
|
|
17495
17963
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17964
|
+
const incidentsPath = errorsPathFor(proj);
|
|
17496
17965
|
try {
|
|
17497
17966
|
const result = await ctx.runPoll(
|
|
17498
17967
|
reg.connector,
|
|
17499
|
-
{
|
|
17968
|
+
{
|
|
17969
|
+
projectDir: proj.scanPath ?? "",
|
|
17970
|
+
credentials: reg.credentials,
|
|
17971
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
17972
|
+
},
|
|
17500
17973
|
proj.graph,
|
|
17501
17974
|
reg.resolveTarget
|
|
17502
17975
|
);
|
|
@@ -17695,7 +18168,7 @@ function registerRoutes(scope, ctx) {
|
|
|
17695
18168
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
17696
18169
|
let violations = await log.readAll();
|
|
17697
18170
|
if (req.query.severity) {
|
|
17698
|
-
const sev =
|
|
18171
|
+
const sev = import_types85.PolicySeveritySchema.safeParse(req.query.severity);
|
|
17699
18172
|
if (!sev.success) {
|
|
17700
18173
|
return reply.code(400).send({
|
|
17701
18174
|
error: "invalid severity",
|
|
@@ -17734,7 +18207,7 @@ function registerRoutes(scope, ctx) {
|
|
|
17734
18207
|
scope.post("/policies/check", async (req, reply) => {
|
|
17735
18208
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
17736
18209
|
if (!proj) return;
|
|
17737
|
-
const parsed =
|
|
18210
|
+
const parsed = import_types85.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
17738
18211
|
if (!parsed.success) {
|
|
17739
18212
|
return reply.code(400).send({
|
|
17740
18213
|
error: "invalid /policies/check body",
|