@canonry/canonry 7.43.2 → 7.44.1
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/assets/agent-workspace/skills/aero/references/agent-operations.md +9 -0
- package/assets/agent-workspace/skills/aero/references/site-health.md +12 -0
- package/assets/agent-workspace/skills/canonry/SKILL.md +9 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +3 -0
- package/assets/assets/{AuditHistoryPanel-C5oZsvsR.js → AuditHistoryPanel-CvANw3ds.js} +1 -1
- package/assets/assets/{BacklinksPage-CF5sGSTQ.js → BacklinksPage-C6x9ELjk.js} +1 -1
- package/assets/assets/{HistoryPage-DumL6cl5.js → HistoryPage-r36HX1cC.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DCegOp_w.js → MeasurementPropertyPage-ComoCzR4.js} +1 -1
- package/assets/assets/{ProjectPage-CWDk48Y6.js → ProjectPage-CIeU-buk.js} +1 -1
- package/assets/assets/{RunsPage-Bb1VMWYu.js → RunsPage-DI6eJATZ.js} +1 -1
- package/assets/assets/{SettingsPage-CHGh169l.js → SettingsPage-Bw7GdXxs.js} +1 -1
- package/assets/assets/{SiteHealthSection-bGzH9m9-.js → SiteHealthSection-C-JaNKpS.js} +3 -3
- package/assets/assets/{TrafficPage-cfnEZ2Mu.js → TrafficPage-Cnsp276v.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BQS0MAXE.js → TrafficSourceDetailPage-CBIaJk7e.js} +1 -1
- package/assets/assets/{extract-error-message-IWhACkO_.js → extract-error-message-C_Owa0n9.js} +1 -1
- package/assets/assets/{index-BDY3-V1S.js → index-ZS9sSTMS.js} +20 -20
- package/assets/assets/{react-sigma_core.esm.min-BQt189AI.js → react-sigma_core.esm.min-ugyh-ooq.js} +1 -1
- package/assets/assets/{v2-overview-adapter-ZC3VuoTT.js → v2-overview-adapter-BLSrVQ7K.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-WU2BRWXY.js → chunk-3JVF3ULD.js} +5 -1
- package/dist/{chunk-WRUKKEGR.js → chunk-45Z5RJZW.js} +3 -3
- package/dist/{chunk-C2I7M3B2.js → chunk-57MGI2UT.js} +127 -9
- package/dist/{chunk-KOIOZ7QW.js → chunk-5LY7EG4U.js} +47 -3
- package/dist/{chunk-65BHDVYO.js → chunk-G47XJVAS.js} +830 -465
- package/dist/{chunk-E43JM3QV.js → chunk-GZ2KIONF.js} +92 -1
- package/dist/{chunk-BWR5MNL5.js → chunk-JVBI6VBT.js} +2 -2
- package/dist/{chunk-WXX3NL4C.js → chunk-LK7O7RM6.js} +62 -19
- package/dist/{chunk-LYKDNJXR.js → chunk-MPWBFKGK.js} +44 -1
- package/dist/{chunk-BPFRGC7K.js → chunk-SW54HXSZ.js} +3 -3
- package/dist/{cli-catalog-J3O6W726.js → cli-catalog-PNJEQV5H.js} +3 -3
- package/dist/cli-commands-D3JIQ2ID.js +15 -0
- package/dist/cli.js +10 -10
- package/dist/{demo-server-KA7DWTY6.js → demo-server-WRHR25HF.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-LHZZTJPH.js → intelligence-service-QXXRUB2G.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +10 -10
- package/dist/cli-commands-QST2CPDG.js +0 -15
|
@@ -154,6 +154,7 @@ import {
|
|
|
154
154
|
SENTIMENT_INTERVAL_LIMITATION,
|
|
155
155
|
SENTIMENT_MODEL,
|
|
156
156
|
SENTIMENT_QUERY_PAGE_DEFAULT,
|
|
157
|
+
SITE_AUDIT_ISSUES_DEFAULT_LIMIT,
|
|
157
158
|
SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES,
|
|
158
159
|
SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES,
|
|
159
160
|
SITE_CRAWL_GRAPH_MAX_EDGES,
|
|
@@ -808,6 +809,9 @@ import {
|
|
|
808
809
|
shiftIsoCalendarDate,
|
|
809
810
|
simpleMeasurementCompetitorNames,
|
|
810
811
|
simpleMeasurementDefinitionSchema,
|
|
812
|
+
siteAuditIssueListQuerySchema,
|
|
813
|
+
siteAuditIssuePagesResponseSchema,
|
|
814
|
+
siteAuditIssuesResponseSchema,
|
|
811
815
|
siteAuditLivePageHealthSchema,
|
|
812
816
|
siteAuditPageFactorSchema,
|
|
813
817
|
siteAuditPagesResponseSchema,
|
|
@@ -818,10 +822,12 @@ import {
|
|
|
818
822
|
siteAuditScoreSchema,
|
|
819
823
|
siteAuditTrendResponseSchema,
|
|
820
824
|
siteCrawlAuditFactorSchema,
|
|
825
|
+
siteCrawlAuditFindingSchema,
|
|
821
826
|
siteCrawlCriticalDefectSchema,
|
|
822
827
|
siteCrawlDeadLinksResponseSchema,
|
|
823
828
|
siteCrawlGraphResponseSchema,
|
|
824
829
|
siteCrawlInternalLinksResponseSchema,
|
|
830
|
+
siteCrawlIssueEvidenceSchema,
|
|
825
831
|
siteCrawlNeighborsResponseSchema,
|
|
826
832
|
siteCrawlPageAuditSchema,
|
|
827
833
|
siteCrawlPagesResponseSchema,
|
|
@@ -905,10 +911,10 @@ import {
|
|
|
905
911
|
wordpressSchemaDeployResultDtoSchema,
|
|
906
912
|
wordpressSchemaStatusResultDtoSchema,
|
|
907
913
|
wordpressStatusDtoSchema
|
|
908
|
-
} from "./chunk-
|
|
914
|
+
} from "./chunk-GZ2KIONF.js";
|
|
909
915
|
|
|
910
916
|
// src/intelligence-service.ts
|
|
911
|
-
import { eq as
|
|
917
|
+
import { eq as eq99, desc as desc45, asc as asc20, and as and82, ne as ne17, or as or24, inArray as inArray40, gte as gte26, lte as lte23, isNull as isNull19, sql as sql37, exists as exists2 } from "drizzle-orm";
|
|
912
918
|
|
|
913
919
|
// ../db/src/client.ts
|
|
914
920
|
import { mkdirSync } from "fs";
|
|
@@ -2094,6 +2100,8 @@ var siteCrawlSnapshots = sqliteTable("site_crawl_snapshots", {
|
|
|
2094
2100
|
pagesErrored: integer("pages_errored").notNull().default(0),
|
|
2095
2101
|
edgesDiscovered: integer("edges_discovered").notNull().default(0),
|
|
2096
2102
|
findingsCount: integer("findings_count").notNull().default(0),
|
|
2103
|
+
/** Null when cross-page issue evidence was not recorded; a measured empty list is distinct. */
|
|
2104
|
+
issueEvidence: text("issue_evidence", { mode: "json" }).$type(),
|
|
2097
2105
|
deadLinkState: text("dead_link_state").notNull().default("disabled"),
|
|
2098
2106
|
deadLinksChecked: integer("dead_links_checked").notNull().default(0),
|
|
2099
2107
|
deadLinksFound: integer("dead_links_found").notNull().default(0),
|
|
@@ -8617,6 +8625,13 @@ var MIGRATION_VERSIONS = [
|
|
|
8617
8625
|
statements: [
|
|
8618
8626
|
`ALTER TABLE doctor_health_state ADD COLUMN pending_signature TEXT`
|
|
8619
8627
|
]
|
|
8628
|
+
},
|
|
8629
|
+
{
|
|
8630
|
+
version: 180,
|
|
8631
|
+
name: "site-crawl-issue-evidence",
|
|
8632
|
+
statements: [
|
|
8633
|
+
`ALTER TABLE site_crawl_snapshots ADD COLUMN issue_evidence TEXT`
|
|
8634
|
+
]
|
|
8620
8635
|
}
|
|
8621
8636
|
];
|
|
8622
8637
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -16038,16 +16053,16 @@ function pruneAutoAliases(entries, project, marketPins) {
|
|
|
16038
16053
|
const autoPlan = planCompetitorAutoAliases(entries.map((entry) => ({
|
|
16039
16054
|
domain: entry.domain,
|
|
16040
16055
|
aliases: entry.aliases,
|
|
16041
|
-
autoAliases: (entry.row?.autoAliases ?? []).map((
|
|
16056
|
+
autoAliases: (entry.row?.autoAliases ?? []).map((record2) => record2.name),
|
|
16042
16057
|
blockedAliases: entry.row?.blockedAliases ?? []
|
|
16043
16058
|
})), project, marketPins);
|
|
16044
16059
|
const autoAliasChanges = [];
|
|
16045
16060
|
const autoAliases = entries.map((entry, index2) => {
|
|
16046
|
-
const before = (entry.row?.autoAliases ?? []).map((
|
|
16061
|
+
const before = (entry.row?.autoAliases ?? []).map((record2) => record2.name);
|
|
16047
16062
|
const keptKeys = new Set(autoPlan.competitors[index2].autoAliases.map(brandKeyFromText));
|
|
16048
|
-
const kept = (entry.row?.autoAliases ?? []).filter((
|
|
16063
|
+
const kept = (entry.row?.autoAliases ?? []).filter((record2) => keptKeys.has(brandKeyFromText(record2.name)));
|
|
16049
16064
|
if (kept.length !== before.length) {
|
|
16050
|
-
autoAliasChanges.push({ domain: entry.domain, before, after: kept.map((
|
|
16065
|
+
autoAliasChanges.push({ domain: entry.domain, before, after: kept.map((record2) => record2.name) });
|
|
16051
16066
|
}
|
|
16052
16067
|
return kept;
|
|
16053
16068
|
});
|
|
@@ -16183,7 +16198,7 @@ function deletedAliasesDiff(rows) {
|
|
|
16183
16198
|
const withBlocked = rows.filter((row) => row.blockedAliases.length > 0);
|
|
16184
16199
|
return {
|
|
16185
16200
|
...withAliases.length ? { deletedAliases: Object.fromEntries(withAliases.map((row) => [row.domain, row.aliases])) } : {},
|
|
16186
|
-
...withAuto.length ? { deletedAutoAliases: Object.fromEntries(withAuto.map((row) => [row.domain, row.autoAliases.map((
|
|
16201
|
+
...withAuto.length ? { deletedAutoAliases: Object.fromEntries(withAuto.map((row) => [row.domain, row.autoAliases.map((record2) => record2.name)])) } : {},
|
|
16187
16202
|
...withBlocked.length ? { deletedBlockedAliases: Object.fromEntries(withBlocked.map((row) => [row.domain, row.blockedAliases])) } : {}
|
|
16188
16203
|
};
|
|
16189
16204
|
}
|
|
@@ -16224,13 +16239,13 @@ function readMarketCompetitorNames(db, projectId, hypotheticalTracked) {
|
|
|
16224
16239
|
...tracked.map((row) => ({
|
|
16225
16240
|
domain: row.domain,
|
|
16226
16241
|
aliases: row.aliases,
|
|
16227
|
-
autoAliases: row.autoAliases.map((
|
|
16242
|
+
autoAliases: row.autoAliases.map((record2) => record2.name),
|
|
16228
16243
|
blockedAliases: row.blockedAliases
|
|
16229
16244
|
})),
|
|
16230
16245
|
...learned.map((entry) => ({
|
|
16231
16246
|
domain: entry.domain,
|
|
16232
16247
|
aliases: entry.names,
|
|
16233
|
-
autoAliases: (entry.stored?.autoAliases ?? []).map((
|
|
16248
|
+
autoAliases: (entry.stored?.autoAliases ?? []).map((record2) => record2.name),
|
|
16234
16249
|
blockedAliases: entry.stored?.blockedAliases ?? []
|
|
16235
16250
|
}))
|
|
16236
16251
|
], competitorAliasProjectIdentity(project), readMarketCompetitorPins(db, projectId));
|
|
@@ -20260,8 +20275,8 @@ async function scanCompetitorAutoAliasEvidence(db, project, opts = {}) {
|
|
|
20260
20275
|
pause,
|
|
20261
20276
|
{
|
|
20262
20277
|
storedNames: [
|
|
20263
|
-
...tracked.flatMap((row) => row.autoAliases.map((
|
|
20264
|
-
...market.flatMap((entry) => (entry.stored?.autoAliases ?? []).map((
|
|
20278
|
+
...tracked.flatMap((row) => row.autoAliases.map((record2) => ({ domain: row.domain, name: record2.name }))),
|
|
20279
|
+
...market.flatMap((entry) => (entry.stored?.autoAliases ?? []).map((record2) => ({ domain: entry.domain, name: record2.name })))
|
|
20265
20280
|
]
|
|
20266
20281
|
}
|
|
20267
20282
|
),
|
|
@@ -22155,8 +22170,8 @@ async function competitorRoutes(app, opts = {}) {
|
|
|
22155
22170
|
});
|
|
22156
22171
|
}
|
|
22157
22172
|
const blockedKeys = new Set(blockedAliases.map(brandKeyFromText));
|
|
22158
|
-
const autoAliases = target2.autoAliases.filter((
|
|
22159
|
-
const removed = target2.autoAliases.filter((
|
|
22173
|
+
const autoAliases = target2.autoAliases.filter((record2) => !blockedKeys.has(brandKeyFromText(record2.name)));
|
|
22174
|
+
const removed = target2.autoAliases.filter((record2) => blockedKeys.has(brandKeyFromText(record2.name))).map((record2) => record2.name);
|
|
22160
22175
|
if (blockedAliases.length === target2.blockedAliases.length && removed.length === 0) {
|
|
22161
22176
|
return { target: target2, namesChanged: false };
|
|
22162
22177
|
}
|
|
@@ -25854,7 +25869,7 @@ function parseCsvRecords(value) {
|
|
|
25854
25869
|
}
|
|
25855
25870
|
if (inQuotes) csvError("csv-malformed", "CSV has an unterminated quoted field.");
|
|
25856
25871
|
if (recordHasInput || fields.length > 0 || field.length > 0) endRecord();
|
|
25857
|
-
return records.filter((
|
|
25872
|
+
return records.filter((record2) => !record2.physicalBlank);
|
|
25858
25873
|
}
|
|
25859
25874
|
function parseHeader(records) {
|
|
25860
25875
|
const header2 = records.at(0);
|
|
@@ -25902,10 +25917,10 @@ function parseGroupMembershipCsv(csv) {
|
|
|
25902
25917
|
const propertyIndex = header2.indices.get("property");
|
|
25903
25918
|
const groupIndex = header2.indices.get("group");
|
|
25904
25919
|
const urlIndex = header2.indices.get("url");
|
|
25905
|
-
const rows = dataRecords.map((
|
|
25906
|
-
const property =
|
|
25907
|
-
const group =
|
|
25908
|
-
const rawUrl = urlIndex === void 0 ? "" :
|
|
25920
|
+
const rows = dataRecords.map((record2, index2) => {
|
|
25921
|
+
const property = record2.fields[propertyIndex] ?? "";
|
|
25922
|
+
const group = record2.fields[groupIndex] ?? "";
|
|
25923
|
+
const rawUrl = urlIndex === void 0 ? "" : record2.fields[urlIndex] ?? "";
|
|
25909
25924
|
const url = rawUrl.trim() || null;
|
|
25910
25925
|
return {
|
|
25911
25926
|
dataRow: index2 + 1,
|
|
@@ -25914,7 +25929,7 @@ function parseGroupMembershipCsv(csv) {
|
|
|
25914
25929
|
url,
|
|
25915
25930
|
normalizedProperty: normalizeGroupMembershipLabel(property),
|
|
25916
25931
|
normalizedGroupLabel: normalizeGroupMembershipLabel(group),
|
|
25917
|
-
columnCountValid:
|
|
25932
|
+
columnCountValid: record2.fields.length === header2.length
|
|
25918
25933
|
};
|
|
25919
25934
|
});
|
|
25920
25935
|
return { sourceChecksum: sourceChecksum(csv), rows };
|
|
@@ -26650,8 +26665,8 @@ function pinCompetitorInAuthoring(authoring, input, trackedAliases = () => []) {
|
|
|
26650
26665
|
}
|
|
26651
26666
|
function nextCompetitorStableKey(group, domain) {
|
|
26652
26667
|
const rawBase = `competitor-${domain}`;
|
|
26653
|
-
const
|
|
26654
|
-
const base = rawBase.length <= 128 ? rawBase : `${rawBase.slice(0, 128 -
|
|
26668
|
+
const digest2 = crypto21.createHash("sha256").update(domain).digest("hex").slice(0, 12);
|
|
26669
|
+
const base = rawBase.length <= 128 ? rawBase : `${rawBase.slice(0, 128 - digest2.length - 1)}-${digest2}`;
|
|
26655
26670
|
const used = new Set(group.competitors.map((competitor) => competitor.stableKey));
|
|
26656
26671
|
if (!used.has(base)) return base;
|
|
26657
26672
|
let suffix = 2;
|
|
@@ -32676,7 +32691,7 @@ function buildSimpleCandidate(db, state, mutation, opts, manualCapturedAt) {
|
|
|
32676
32691
|
assertSimpleAddition(addition);
|
|
32677
32692
|
const audience = { targetKeys: [], groupKeys: [], marketKeys: [] };
|
|
32678
32693
|
const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt);
|
|
32679
|
-
for (const
|
|
32694
|
+
for (const record2 of records) addSimpleSource(state, rows, record2, reusedQueryIds);
|
|
32680
32695
|
}
|
|
32681
32696
|
const planChanged = false;
|
|
32682
32697
|
const rowChanged = !rowsEqual(new Map(state.queryRows.map((row) => [row.id, row])), rows);
|
|
@@ -32798,13 +32813,13 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
|
|
|
32798
32813
|
return { edge, assignment, node, target, queryClass: proposed, classificationSource, scopes };
|
|
32799
32814
|
});
|
|
32800
32815
|
if (!textChanged) {
|
|
32801
|
-
for (const
|
|
32802
|
-
if (
|
|
32803
|
-
if (marketKeys.size > 0 &&
|
|
32816
|
+
for (const record2 of records) {
|
|
32817
|
+
if (record2.assignment.queryClass === record2.queryClass && record2.assignment.classificationSource === record2.classificationSource) continue;
|
|
32818
|
+
if (marketKeys.size > 0 && record2.scopes.some((scope) => !marketKeys.has(scope.stableKey))) {
|
|
32804
32819
|
throw validationError("This assignment is shared with another market. Change its classification from the Property or Whole site scope.");
|
|
32805
32820
|
}
|
|
32806
|
-
|
|
32807
|
-
|
|
32821
|
+
record2.assignment.queryClass = record2.queryClass;
|
|
32822
|
+
record2.assignment.classificationSource = record2.classificationSource;
|
|
32808
32823
|
mutatedQueryIds.add(edit.queryId);
|
|
32809
32824
|
}
|
|
32810
32825
|
return plan;
|
|
@@ -32818,22 +32833,22 @@ function applyAdvancedEdit(state, plan, rows, edit, capturedAt, reusedQueryIds,
|
|
|
32818
32833
|
const selectedTargets = new Set(selectedEdges.map((edge) => edge.targetKey));
|
|
32819
32834
|
plan = marketKeys.size > 0 ? removeMarketMembership(plan, edit.queryId, selectedTargets, marketKeys) : removeAdvancedAssignments(plan, edit.queryId, selectedTargets);
|
|
32820
32835
|
if (!planSnapshot(plan, newQueryId)) plan.querySnapshots.push({ queryId: newQueryId, queryText, provenance });
|
|
32821
|
-
for (const
|
|
32822
|
-
const existingNode = plan.executionNodes.find((node) => node.queryId === newQueryId && contextKey(node.context) === contextKey(
|
|
32823
|
-
const existingAssignment = existingNode ? plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(
|
|
32824
|
-
if (existingAssignment && (existingAssignment.queryClass !==
|
|
32836
|
+
for (const record2 of records) {
|
|
32837
|
+
const existingNode = plan.executionNodes.find((node) => node.queryId === newQueryId && contextKey(node.context) === contextKey(record2.node.context));
|
|
32838
|
+
const existingAssignment = existingNode ? plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(record2.target.stableKey, newQueryId, existingNode.stableKey)) : void 0;
|
|
32839
|
+
if (existingAssignment && (existingAssignment.queryClass !== record2.queryClass || existingAssignment.classificationSource !== record2.classificationSource)) {
|
|
32825
32840
|
throw validationError("The destination query uses different classification settings. Review that query separately.");
|
|
32826
32841
|
}
|
|
32827
|
-
const edge = existingNode ? { queryId: newQueryId, targetKey:
|
|
32828
|
-
if (existingNode && !existingAssignment) plan.assignments.push({ ...edge, queryClass:
|
|
32842
|
+
const edge = existingNode ? { queryId: newQueryId, targetKey: record2.target.stableKey, executionNodeKey: existingNode.stableKey } : ensureAdvancedEdge(plan, state, record2.target, newQueryId, queryText, record2.queryClass, record2.classificationSource ?? "server", record2.node.context);
|
|
32843
|
+
if (existingNode && !existingAssignment) plan.assignments.push({ ...edge, queryClass: record2.queryClass, ...record2.classificationSource ? { classificationSource: record2.classificationSource } : {} });
|
|
32829
32844
|
if (!existingNode) {
|
|
32830
32845
|
const createdNode = plan.executionNodes.find((node) => node.stableKey === edge.executionNodeKey);
|
|
32831
|
-
if (contextKey(createdNode.context) !== contextKey(
|
|
32846
|
+
if (contextKey(createdNode.context) !== contextKey(record2.node.context)) throw validationError("The edited query cannot preserve its stored execution context.");
|
|
32832
32847
|
const createdAssignment = plan.assignments.find((candidate) => assignmentKey(candidate.targetKey, candidate.queryId, candidate.executionNodeKey) === assignmentKey(edge.targetKey, edge.queryId, edge.executionNodeKey));
|
|
32833
|
-
if (
|
|
32848
|
+
if (record2.classificationSource === void 0) delete createdAssignment.classificationSource;
|
|
32834
32849
|
}
|
|
32835
32850
|
if (!plan.usageEdges.some((candidate) => edgeKey(candidate) === edgeKey(edge))) plan.usageEdges.push(edge);
|
|
32836
|
-
for (const scope of
|
|
32851
|
+
for (const scope of record2.scopes) {
|
|
32837
32852
|
if (marketKeys.size === 0 || marketKeys.has(scope.stableKey)) addMarketEdge(plan, scope.stableKey, edge);
|
|
32838
32853
|
}
|
|
32839
32854
|
}
|
|
@@ -32874,9 +32889,9 @@ function buildAdvancedCandidate(db, state, mutation, manualCapturedAt) {
|
|
|
32874
32889
|
}
|
|
32875
32890
|
for (const addition of mutation.additions) {
|
|
32876
32891
|
const audience = resolveAudience(active.plan, addition.audience);
|
|
32877
|
-
const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt).map((
|
|
32878
|
-
for (const
|
|
32879
|
-
addAdvancedSource(state, plan, rows, addition,
|
|
32892
|
+
const records = addition.input.source === "template" ? templateRecords(db, state, addition, audience) : sourceRecord(db, state.project.id, addition, manualCapturedAt).map((record2) => ({ ...record2, audience }));
|
|
32893
|
+
for (const record2 of records) {
|
|
32894
|
+
addAdvancedSource(state, plan, rows, addition, record2, reusedQueryIds, mutatedQueryIds);
|
|
32880
32895
|
}
|
|
32881
32896
|
}
|
|
32882
32897
|
for (const queryId of fullRemovals) {
|
|
@@ -38198,41 +38213,41 @@ function csvCell(value) {
|
|
|
38198
38213
|
return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
|
|
38199
38214
|
}
|
|
38200
38215
|
function renderResultsExportCsv(report) {
|
|
38201
|
-
const rows = report.records.map((
|
|
38216
|
+
const rows = report.records.map((record2) => [
|
|
38202
38217
|
report.schemaVersion,
|
|
38203
38218
|
report.project.id,
|
|
38204
38219
|
report.project.name,
|
|
38205
38220
|
report.project.displayName,
|
|
38206
38221
|
report.project.canonicalDomain,
|
|
38207
|
-
|
|
38208
|
-
|
|
38209
|
-
|
|
38210
|
-
|
|
38211
|
-
|
|
38212
|
-
|
|
38213
|
-
|
|
38214
|
-
|
|
38215
|
-
|
|
38216
|
-
|
|
38217
|
-
|
|
38218
|
-
|
|
38219
|
-
|
|
38220
|
-
|
|
38221
|
-
|
|
38222
|
-
|
|
38223
|
-
|
|
38224
|
-
|
|
38225
|
-
JSON.stringify(
|
|
38226
|
-
JSON.stringify(
|
|
38227
|
-
|
|
38228
|
-
|
|
38229
|
-
|
|
38230
|
-
|
|
38231
|
-
JSON.stringify(
|
|
38232
|
-
JSON.stringify(
|
|
38233
|
-
|
|
38234
|
-
JSON.stringify(
|
|
38235
|
-
JSON.stringify(
|
|
38222
|
+
record2.runId,
|
|
38223
|
+
record2.runKind,
|
|
38224
|
+
record2.runStatus,
|
|
38225
|
+
record2.runTrigger,
|
|
38226
|
+
record2.runCreatedAt,
|
|
38227
|
+
record2.runStartedAt,
|
|
38228
|
+
record2.runFinishedAt,
|
|
38229
|
+
record2.snapshotId,
|
|
38230
|
+
record2.snapshotCreatedAt,
|
|
38231
|
+
record2.queryId,
|
|
38232
|
+
record2.query,
|
|
38233
|
+
record2.provider,
|
|
38234
|
+
record2.model,
|
|
38235
|
+
record2.location,
|
|
38236
|
+
record2.citationState,
|
|
38237
|
+
record2.cited,
|
|
38238
|
+
record2.answerMentioned,
|
|
38239
|
+
record2.mentionState,
|
|
38240
|
+
JSON.stringify(record2.citedDomains),
|
|
38241
|
+
JSON.stringify(record2.citedUrls),
|
|
38242
|
+
record2.captureStatus,
|
|
38243
|
+
record2.sourceCount,
|
|
38244
|
+
record2.resolvedCount,
|
|
38245
|
+
record2.captureVersion,
|
|
38246
|
+
JSON.stringify(record2.competitorOverlap),
|
|
38247
|
+
JSON.stringify(record2.recommendedCompetitors),
|
|
38248
|
+
record2.answerText,
|
|
38249
|
+
JSON.stringify(record2.groundingSources),
|
|
38250
|
+
JSON.stringify(record2.searchQueries)
|
|
38236
38251
|
].map(csvCell).join(","));
|
|
38237
38252
|
return [CSV_COLUMNS.join(","), ...rows].join("\r\n") + "\r\n";
|
|
38238
38253
|
}
|
|
@@ -40432,6 +40447,8 @@ var SCHEMA_TABLE = {
|
|
|
40432
40447
|
CalendarRecurrence: calendarRecurrenceSchema,
|
|
40433
40448
|
ScheduleDto: scheduleDtoSchema,
|
|
40434
40449
|
SettingsDto: settingsDtoSchema,
|
|
40450
|
+
SiteAuditIssuePagesResponseDto: siteAuditIssuePagesResponseSchema,
|
|
40451
|
+
SiteAuditIssuesResponseDto: siteAuditIssuesResponseSchema,
|
|
40435
40452
|
SiteAuditLivePageHealthDto: siteAuditLivePageHealthSchema,
|
|
40436
40453
|
SiteAuditPagesResponseDto: siteAuditPagesResponseSchema,
|
|
40437
40454
|
SiteAuditRunProgressDto: siteAuditRunProgressSchema,
|
|
@@ -40511,12 +40528,12 @@ function addNullToNullableEnums(node) {
|
|
|
40511
40528
|
return;
|
|
40512
40529
|
}
|
|
40513
40530
|
if (node === null || typeof node !== "object") return;
|
|
40514
|
-
const
|
|
40515
|
-
if (
|
|
40516
|
-
const values =
|
|
40517
|
-
if (!values.includes(null))
|
|
40531
|
+
const record2 = node;
|
|
40532
|
+
if (record2.nullable === true && Array.isArray(record2.enum)) {
|
|
40533
|
+
const values = record2.enum;
|
|
40534
|
+
if (!values.includes(null)) record2.enum = [...values, null];
|
|
40518
40535
|
}
|
|
40519
|
-
for (const value of Object.values(
|
|
40536
|
+
for (const value of Object.values(record2)) addNullToNullableEnums(value);
|
|
40520
40537
|
}
|
|
40521
40538
|
var INPUT_FORM_SCHEMAS = /* @__PURE__ */ new Set(["ProjectConfig"]);
|
|
40522
40539
|
function buildComponentSchemas() {
|
|
@@ -47971,6 +47988,43 @@ var routeCatalog = [
|
|
|
47971
47988
|
404: errorResponse("Project or selected crawl-bearing run not found.")
|
|
47972
47989
|
}
|
|
47973
47990
|
},
|
|
47991
|
+
{
|
|
47992
|
+
method: "get",
|
|
47993
|
+
path: "/api/v1/projects/{name}/technical-aeo/issues",
|
|
47994
|
+
summary: "List stored audit issue groups and representative URLs",
|
|
47995
|
+
description: "Read-only retained evidence, without provider calls. Includes coded findings and independent critical defects at every page score and retained cross-page groups; mirrored duplicate annotations appear once. Returns stored run/date/attempt, crawl completeness, and separate page-finding/cross-page coverage so legacy absence and malformed evidence cannot imply a clean site. Disposition is a prioritization hint, not permission to edit or proof of score impact. Recommendations distinguish whole-factor guidance from independently issue-scoped critical-defect guidance. Page-finding identities use factor/code/type; duplicate metadata identities use the full exact metadata value. Unknown cross-page identities are run-local. Reads fail explicitly above 128 MiB evidence or 4096 groups; they never sample an apparently complete total.",
|
|
47996
|
+
tags: ["technical-aeo"],
|
|
47997
|
+
parameters: [
|
|
47998
|
+
nameParameter,
|
|
47999
|
+
{ name: "runId", in: "query", description: "Stored non-probe audit run. Omit for the standard latest crawl selection.", schema: stringSchema },
|
|
48000
|
+
{ name: "cursor", in: "query", description: "Opaque continuation bound to project, run, published attempt, issue, and exact retained evidence.", schema: { type: "string", maxLength: 2048 } },
|
|
48001
|
+
{ name: "limit", in: "query", description: "Maximum rows; the 16 KiB serialized response budget may return fewer. Follow nextCursor until null.", schema: { type: "integer", minimum: 1, maximum: 100, default: 25 } }
|
|
48002
|
+
],
|
|
48003
|
+
responses: {
|
|
48004
|
+
200: jsonResponse("Stored issue groups and coverage returned.", "SiteAuditIssuesResponseDto"),
|
|
48005
|
+
400: errorResponse("Invalid query/cursor or evidence/response read budget exceeded."),
|
|
48006
|
+
404: errorResponse("Project or explicitly selected non-probe audit run not found.")
|
|
48007
|
+
}
|
|
48008
|
+
},
|
|
48009
|
+
{
|
|
48010
|
+
method: "get",
|
|
48011
|
+
path: "/api/v1/projects/{name}/technical-aeo/issues/{issueId}/pages",
|
|
48012
|
+
summary: "List affected URLs for one stored audit issue",
|
|
48013
|
+
description: "Read-only bounded membership of one issue in the selected published audit attempt. Preserve runId from the issue list when following historical evidence. Messages and factor-scoped recommendations expose explicit truncation; URLs are preserved. Missing legacy evidence remains unavailable, never a measured empty result.",
|
|
48014
|
+
tags: ["technical-aeo"],
|
|
48015
|
+
parameters: [
|
|
48016
|
+
nameParameter,
|
|
48017
|
+
{ name: "runId", in: "query", description: "Stored non-probe audit run. Omit for the standard latest crawl selection.", schema: stringSchema },
|
|
48018
|
+
{ name: "cursor", in: "query", description: "Opaque continuation bound to project, run, published attempt, issue, and exact retained evidence.", schema: { type: "string", maxLength: 2048 } },
|
|
48019
|
+
{ name: "limit", in: "query", description: "Maximum rows; the 16 KiB serialized response budget may return fewer. Follow nextCursor until null.", schema: { type: "integer", minimum: 1, maximum: 100, default: 25 } },
|
|
48020
|
+
{ name: "issueId", in: "path", required: true, description: "Exact issue ID from the stored issue list.", schema: stringSchema }
|
|
48021
|
+
],
|
|
48022
|
+
responses: {
|
|
48023
|
+
200: jsonResponse("Stored affected page evidence and coverage returned.", "SiteAuditIssuePagesResponseDto"),
|
|
48024
|
+
400: errorResponse("Invalid query/cursor or evidence/response read budget exceeded."),
|
|
48025
|
+
404: errorResponse("Project, selected non-probe audit run, or issue not found.")
|
|
48026
|
+
}
|
|
48027
|
+
},
|
|
47974
48028
|
{
|
|
47975
48029
|
method: "get",
|
|
47976
48030
|
path: "/api/v1/projects/{name}/technical-aeo/crawl/pages/audit",
|
|
@@ -64629,7 +64683,7 @@ async function queryBacklinks(opts) {
|
|
|
64629
64683
|
const reversed = opts.targets.map(reverseDomain);
|
|
64630
64684
|
const targetList = reversed.map(quote).join(", ");
|
|
64631
64685
|
const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
|
|
64632
|
-
const
|
|
64686
|
+
const sql38 = `
|
|
64633
64687
|
WITH vertices AS (
|
|
64634
64688
|
SELECT * FROM read_csv(
|
|
64635
64689
|
${quote(opts.vertexPath)},
|
|
@@ -64665,7 +64719,7 @@ async function queryBacklinks(opts) {
|
|
|
64665
64719
|
const conn = await instance.connect();
|
|
64666
64720
|
let rows;
|
|
64667
64721
|
try {
|
|
64668
|
-
const reader = await conn.runAndReadAll(
|
|
64722
|
+
const reader = await conn.runAndReadAll(sql38);
|
|
64669
64723
|
rows = reader.getRowObjects();
|
|
64670
64724
|
} finally {
|
|
64671
64725
|
conn.disconnectSync?.();
|
|
@@ -79363,9 +79417,9 @@ var RULE_ID_TO_RANGES = {
|
|
|
79363
79417
|
};
|
|
79364
79418
|
function manifestMetadata(raw) {
|
|
79365
79419
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
79366
|
-
const
|
|
79367
|
-
const source = typeof
|
|
79368
|
-
const version = typeof
|
|
79420
|
+
const record2 = raw;
|
|
79421
|
+
const source = typeof record2._source === "string" ? record2._source.trim() : "";
|
|
79422
|
+
const version = typeof record2.creationTime === "string" ? record2.creationTime.trim() : "";
|
|
79369
79423
|
if (!source || !version) return null;
|
|
79370
79424
|
const contentHash2 = ipRangeManifestContentHash(raw);
|
|
79371
79425
|
return { id: `${source}#${version}#sha256:${contentHash2}`, source, version };
|
|
@@ -80911,10 +80965,10 @@ function canonicalizeCloudflareJson(value) {
|
|
|
80911
80965
|
return `[${value.map((item2) => canonicalizeCloudflareJson(item2)).join(",")}]`;
|
|
80912
80966
|
}
|
|
80913
80967
|
if (typeof value === "object") {
|
|
80914
|
-
const
|
|
80968
|
+
const record2 = value;
|
|
80915
80969
|
const fields = [];
|
|
80916
|
-
for (const key of Object.keys(
|
|
80917
|
-
const fieldValue =
|
|
80970
|
+
for (const key of Object.keys(record2).sort()) {
|
|
80971
|
+
const fieldValue = record2[key];
|
|
80918
80972
|
if (fieldValue === void 0) continue;
|
|
80919
80973
|
fields.push(`${JSON.stringify(key)}:${canonicalizeCloudflareJson(fieldValue)}`);
|
|
80920
80974
|
}
|
|
@@ -81587,22 +81641,22 @@ function asRecord(value, label) {
|
|
|
81587
81641
|
}
|
|
81588
81642
|
return value;
|
|
81589
81643
|
}
|
|
81590
|
-
function requiredString2(
|
|
81591
|
-
const value =
|
|
81644
|
+
function requiredString2(record2, key, label) {
|
|
81645
|
+
const value = record2[key];
|
|
81592
81646
|
if (typeof value !== "string" || !value.trim()) {
|
|
81593
81647
|
throw new CloudflareQueueApiError(`Cloudflare Queue ${label} is malformed`, 502);
|
|
81594
81648
|
}
|
|
81595
81649
|
return value;
|
|
81596
81650
|
}
|
|
81597
|
-
function stringField(
|
|
81598
|
-
const value =
|
|
81651
|
+
function stringField(record2, key, label) {
|
|
81652
|
+
const value = record2[key];
|
|
81599
81653
|
if (typeof value !== "string") {
|
|
81600
81654
|
throw new CloudflareQueueApiError(`Cloudflare Queue ${label} is malformed`, 502);
|
|
81601
81655
|
}
|
|
81602
81656
|
return value;
|
|
81603
81657
|
}
|
|
81604
|
-
function metadata(
|
|
81605
|
-
const value = asRecord(
|
|
81658
|
+
function metadata(record2) {
|
|
81659
|
+
const value = asRecord(record2.metadata, "message metadata");
|
|
81606
81660
|
const parsed = {};
|
|
81607
81661
|
for (const [key, entry] of Object.entries(value)) {
|
|
81608
81662
|
if (typeof entry !== "string") {
|
|
@@ -81639,8 +81693,8 @@ function decodeJson(value) {
|
|
|
81639
81693
|
return parseJson(decodeUtf8(decodeBase64(value)));
|
|
81640
81694
|
}
|
|
81641
81695
|
}
|
|
81642
|
-
function safeMessageBase(
|
|
81643
|
-
const rawMetadata =
|
|
81696
|
+
function safeMessageBase(record2, leaseId) {
|
|
81697
|
+
const rawMetadata = record2.metadata;
|
|
81644
81698
|
const safeMetadata = {};
|
|
81645
81699
|
let metadataIsSafe = rawMetadata != null && typeof rawMetadata === "object" && !Array.isArray(rawMetadata);
|
|
81646
81700
|
if (metadataIsSafe) {
|
|
@@ -81653,30 +81707,30 @@ function safeMessageBase(record, leaseId) {
|
|
|
81653
81707
|
}
|
|
81654
81708
|
}
|
|
81655
81709
|
return {
|
|
81656
|
-
id: typeof
|
|
81710
|
+
id: typeof record2.id === "string" && record2.id.trim() ? record2.id : "<malformed>",
|
|
81657
81711
|
leaseId,
|
|
81658
|
-
timestampMs: Number.isInteger(
|
|
81659
|
-
attempts: Number.isInteger(
|
|
81712
|
+
timestampMs: Number.isInteger(record2.timestamp_ms) && record2.timestamp_ms >= 0 ? record2.timestamp_ms : 0,
|
|
81713
|
+
attempts: Number.isInteger(record2.attempts) && record2.attempts >= 0 ? record2.attempts : 0,
|
|
81660
81714
|
metadata: metadataIsSafe ? safeMetadata : {}
|
|
81661
81715
|
};
|
|
81662
81716
|
}
|
|
81663
|
-
function decodeMessage(
|
|
81664
|
-
const leaseId = requiredString2(
|
|
81717
|
+
function decodeMessage(record2) {
|
|
81718
|
+
const leaseId = requiredString2(record2, "lease_id", "message");
|
|
81665
81719
|
let base;
|
|
81666
81720
|
let rawBody;
|
|
81667
81721
|
try {
|
|
81668
81722
|
base = {
|
|
81669
|
-
id: requiredString2(
|
|
81723
|
+
id: requiredString2(record2, "id", "message"),
|
|
81670
81724
|
leaseId,
|
|
81671
|
-
timestampMs: nonNegativeInteger(
|
|
81672
|
-
attempts: nonNegativeInteger(
|
|
81673
|
-
metadata: metadata(
|
|
81725
|
+
timestampMs: nonNegativeInteger(record2.timestamp_ms, "message timestamp_ms"),
|
|
81726
|
+
attempts: nonNegativeInteger(record2.attempts, "message attempts"),
|
|
81727
|
+
metadata: metadata(record2)
|
|
81674
81728
|
};
|
|
81675
|
-
rawBody = stringField(
|
|
81729
|
+
rawBody = stringField(record2, "body", "message");
|
|
81676
81730
|
} catch (error) {
|
|
81677
81731
|
if (!(error instanceof CloudflareQueueApiError)) throw error;
|
|
81678
81732
|
return {
|
|
81679
|
-
...safeMessageBase(
|
|
81733
|
+
...safeMessageBase(record2, leaseId),
|
|
81680
81734
|
contentType: "poison",
|
|
81681
81735
|
reason: "malformed-envelope"
|
|
81682
81736
|
};
|
|
@@ -81735,9 +81789,9 @@ function parsePullEnvelope(value) {
|
|
|
81735
81789
|
}
|
|
81736
81790
|
return { messageBacklogCount, messages, skippedUnleasedMessageCount };
|
|
81737
81791
|
}
|
|
81738
|
-
function optionalAckCount(
|
|
81739
|
-
if (!(key in
|
|
81740
|
-
return nonNegativeInteger(
|
|
81792
|
+
function optionalAckCount(record2, key) {
|
|
81793
|
+
if (!(key in record2)) return void 0;
|
|
81794
|
+
return nonNegativeInteger(record2[key], `ack response ${key}`);
|
|
81741
81795
|
}
|
|
81742
81796
|
function ackWarningCount(value) {
|
|
81743
81797
|
if (value == null) return 0;
|
|
@@ -82486,12 +82540,12 @@ function rowToDto(row) {
|
|
|
82486
82540
|
updatedAt: row.updatedAt
|
|
82487
82541
|
};
|
|
82488
82542
|
}
|
|
82489
|
-
async function defaultResolveAccessToken(
|
|
82490
|
-
if (
|
|
82491
|
-
if (!
|
|
82543
|
+
async function defaultResolveAccessToken(record2) {
|
|
82544
|
+
if (record2.authMode === TrafficSourceAuthModes["service-account"]) {
|
|
82545
|
+
if (!record2.clientEmail || !record2.privateKey) {
|
|
82492
82546
|
throw validationError("Service-account credentials missing client_email or private_key");
|
|
82493
82547
|
}
|
|
82494
|
-
return getCloudLoggingAccessToken(
|
|
82548
|
+
return getCloudLoggingAccessToken(record2.clientEmail, record2.privateKey);
|
|
82495
82549
|
}
|
|
82496
82550
|
throw validationError(
|
|
82497
82551
|
"OAuth-mode Cloud Run sync is not yet supported in v1. Provide a service-account key file."
|
|
@@ -87155,16 +87209,16 @@ async function probeApiHostUncached(host, options) {
|
|
|
87155
87209
|
const result2 = { kind: kind2, host, addresses: [], code, durationMs: now() - started };
|
|
87156
87210
|
return result2;
|
|
87157
87211
|
}
|
|
87158
|
-
const addresses = records.map((
|
|
87159
|
-
const publicAddresses = records.filter((
|
|
87160
|
-
const blocked = records.map((
|
|
87212
|
+
const addresses = records.map((record2) => record2.address);
|
|
87213
|
+
const publicAddresses = records.filter((record2) => net5.isIP(record2.address) > 0 && !classifyBlocked(record2.address)).slice(0, 2);
|
|
87214
|
+
const blocked = records.map((record2) => classifyBlocked(record2.address)).find(Boolean);
|
|
87161
87215
|
if (publicAddresses.length === 0) {
|
|
87162
87216
|
const result2 = { kind: blocked ?? "no-address", host, addresses, durationMs: now() - started };
|
|
87163
87217
|
return result2;
|
|
87164
87218
|
}
|
|
87165
|
-
const attempts = await Promise.all(publicAddresses.map(async (
|
|
87219
|
+
const attempts = await Promise.all(publicAddresses.map(async (record2) => {
|
|
87166
87220
|
try {
|
|
87167
|
-
return await connect(host,
|
|
87221
|
+
return await connect(host, record2.address, options.timeoutMs ?? DEFAULT_TIMEOUT_MS6);
|
|
87168
87222
|
} catch (error) {
|
|
87169
87223
|
return isTlsProbeError(error) ? "tls-failed" : "connect-failed";
|
|
87170
87224
|
}
|
|
@@ -90613,8 +90667,299 @@ function dedupeStrings(input) {
|
|
|
90613
90667
|
|
|
90614
90668
|
// ../api-routes/src/technical-aeo.ts
|
|
90615
90669
|
import crypto53 from "crypto";
|
|
90616
|
-
import { and as
|
|
90670
|
+
import { and as and78, asc as asc18, count as count7, desc as desc43, eq as eq95, gte as gte24, inArray as inArray38, isNotNull as isNotNull9, isNull as isNull18, lt as lt13, or as or22, sql as sql35 } from "drizzle-orm";
|
|
90617
90671
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
90672
|
+
|
|
90673
|
+
// ../api-routes/src/technical-aeo-issues.ts
|
|
90674
|
+
import { createHash as createHash11 } from "crypto";
|
|
90675
|
+
import { and as and77, asc as asc17, eq as eq94, gt as gt8, sql as sql34 } from "drizzle-orm";
|
|
90676
|
+
var ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
90677
|
+
"entity-consistency.title.short",
|
|
90678
|
+
"site.duplicate-title",
|
|
90679
|
+
"site.duplicate-meta-description"
|
|
90680
|
+
]);
|
|
90681
|
+
var DISPOSITION_ORDER = { actionable: 0, advisory: 1, unverified: 2, informational: 3 };
|
|
90682
|
+
var MAX_EVIDENCE_BYTES = 128 * 1024 * 1024;
|
|
90683
|
+
var MAX_GROUPS = 4096;
|
|
90684
|
+
var MAX_RESPONSE_BYTES = 16 * 1024;
|
|
90685
|
+
function digest(value) {
|
|
90686
|
+
return createHash11("sha256").update(JSON.stringify(value)).digest("hex");
|
|
90687
|
+
}
|
|
90688
|
+
function record(value) {
|
|
90689
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
90690
|
+
}
|
|
90691
|
+
function clip2(message) {
|
|
90692
|
+
return { message: message.slice(0, 400), messageTruncated: message.length > 400 };
|
|
90693
|
+
}
|
|
90694
|
+
function parseSiteAuditIssueQuery(query) {
|
|
90695
|
+
const limit = query.limit;
|
|
90696
|
+
const parsed = siteAuditIssueListQuerySchema.safeParse({
|
|
90697
|
+
...query,
|
|
90698
|
+
...limit === void 0 ? {} : { limit: typeof limit === "string" && limit.trim() !== "" ? Number(limit) : limit }
|
|
90699
|
+
});
|
|
90700
|
+
if (!parsed.success) throw validationError(parsed.error.issues.map((issue) => issue.message).join("; "));
|
|
90701
|
+
return parsed.data;
|
|
90702
|
+
}
|
|
90703
|
+
function cursorOffset(cursor) {
|
|
90704
|
+
if (!cursor) return 0;
|
|
90705
|
+
try {
|
|
90706
|
+
const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
90707
|
+
const value = record(decoded);
|
|
90708
|
+
if (!value || value.v !== 1 || !Number.isSafeInteger(value.offset) || Number(value.offset) < 1 || typeof value.check !== "string" || Object.keys(value).length !== 3) throw new Error("cursor");
|
|
90709
|
+
if (Buffer.from(JSON.stringify(value)).toString("base64url") !== cursor) throw new Error("cursor");
|
|
90710
|
+
return Number(value.offset);
|
|
90711
|
+
} catch {
|
|
90712
|
+
throw validationError("Invalid stored audit issue cursor");
|
|
90713
|
+
}
|
|
90714
|
+
}
|
|
90715
|
+
function encodeCursor2(basis, offset) {
|
|
90716
|
+
return Buffer.from(JSON.stringify({ v: 1, offset, check: digest([basis, offset]) })).toString("base64url");
|
|
90717
|
+
}
|
|
90718
|
+
function pageEvidence(nodeKey, url, message, factor) {
|
|
90719
|
+
const recommendations = Array.isArray(factor?.recommendations) ? factor.recommendations.filter((value) => typeof value === "string") : [];
|
|
90720
|
+
return {
|
|
90721
|
+
nodeKey,
|
|
90722
|
+
url,
|
|
90723
|
+
...clip2(message),
|
|
90724
|
+
recommendations: recommendations.slice(0, 3).map((value) => value.slice(0, 400)),
|
|
90725
|
+
recommendationsScope: factor ? "factor" : "none",
|
|
90726
|
+
recommendationsTruncated: recommendations.length > 3 || recommendations.some((value) => value.length > 400)
|
|
90727
|
+
};
|
|
90728
|
+
}
|
|
90729
|
+
function readSiteAuditIssues(input) {
|
|
90730
|
+
const { db, project, target, query, issueId } = input;
|
|
90731
|
+
const limit = query.limit ?? SITE_AUDIT_ISSUES_DEFAULT_LIMIT;
|
|
90732
|
+
const offset = cursorOffset(query.cursor);
|
|
90733
|
+
const coverage = {
|
|
90734
|
+
pageFindings: "unavailable",
|
|
90735
|
+
crossPage: "unavailable",
|
|
90736
|
+
auditedPages: 0,
|
|
90737
|
+
pagesWithFindingEvidence: 0,
|
|
90738
|
+
pagesWithoutFindingEvidence: 0,
|
|
90739
|
+
omittedFindings: 0
|
|
90740
|
+
};
|
|
90741
|
+
const provenance = {
|
|
90742
|
+
project: project.name,
|
|
90743
|
+
runId: target?.snapshot.runId ?? null,
|
|
90744
|
+
attemptId: target?.snapshot.attemptId ?? null,
|
|
90745
|
+
auditedAt: target?.snapshot.createdAt ?? null,
|
|
90746
|
+
runStatus: target?.runStatus ?? null,
|
|
90747
|
+
complete: target?.snapshot.complete ?? false,
|
|
90748
|
+
termination: target?.snapshot.termination ?? null,
|
|
90749
|
+
...target ? { runSelection: target.runSelection } : {},
|
|
90750
|
+
state: target ? "unavailable" : "no-crawl",
|
|
90751
|
+
coverage,
|
|
90752
|
+
total: null,
|
|
90753
|
+
nextCursor: null
|
|
90754
|
+
};
|
|
90755
|
+
if (!target) {
|
|
90756
|
+
if (query.cursor) throw validationError("Stored audit issue cursor no longer identifies a crawl");
|
|
90757
|
+
const response2 = issueId === void 0 ? { ...provenance, issues: [] } : { ...provenance, issueId, pages: [] };
|
|
90758
|
+
if (Buffer.byteLength(JSON.stringify(response2, null, 2)) > MAX_RESPONSE_BYTES) throw validationError("Stored audit issue exceeds the 16 KiB response budget");
|
|
90759
|
+
return response2;
|
|
90760
|
+
}
|
|
90761
|
+
const snapshot = target.snapshot;
|
|
90762
|
+
const crossJson = JSON.stringify(snapshot.issueEvidence);
|
|
90763
|
+
const crossBytes = Buffer.byteLength(crossJson);
|
|
90764
|
+
if (crossBytes > MAX_EVIDENCE_BYTES) throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
|
|
90765
|
+
const groups = /* @__PURE__ */ new Map();
|
|
90766
|
+
const members = [];
|
|
90767
|
+
let selectedMembers = 0;
|
|
90768
|
+
const evidenceHash = createHash11("sha256").update(JSON.stringify([
|
|
90769
|
+
project.id,
|
|
90770
|
+
snapshot.runId,
|
|
90771
|
+
snapshot.attemptId,
|
|
90772
|
+
snapshot.createdAt,
|
|
90773
|
+
snapshot.updatedAt,
|
|
90774
|
+
snapshot.complete,
|
|
90775
|
+
snapshot.termination,
|
|
90776
|
+
snapshot.detailsAvailable,
|
|
90777
|
+
issueId ?? null
|
|
90778
|
+
])).update(crossJson);
|
|
90779
|
+
const addMember = (group, page3) => {
|
|
90780
|
+
group.affectedPages += 1;
|
|
90781
|
+
if (group.examples.length < 3) group.examples.push(page3);
|
|
90782
|
+
else group.examplesTruncated = true;
|
|
90783
|
+
if (group.id === issueId) {
|
|
90784
|
+
if (selectedMembers >= offset && members.length < limit + 1) members.push(page3);
|
|
90785
|
+
selectedMembers += 1;
|
|
90786
|
+
}
|
|
90787
|
+
};
|
|
90788
|
+
const addGroup = (group) => {
|
|
90789
|
+
const existing = groups.get(group.id);
|
|
90790
|
+
if (existing) return existing;
|
|
90791
|
+
if (groups.size >= MAX_GROUPS) throw validationError("Stored audit exceeds the 4096 issue-group read budget");
|
|
90792
|
+
groups.set(group.id, group);
|
|
90793
|
+
return group;
|
|
90794
|
+
};
|
|
90795
|
+
const cross = siteCrawlIssueEvidenceSchema.safeParse(snapshot.issueEvidence);
|
|
90796
|
+
const mirroredCodes = /* @__PURE__ */ new Set();
|
|
90797
|
+
if (cross.success) {
|
|
90798
|
+
coverage.crossPage = "available";
|
|
90799
|
+
for (const [index2, issue] of cross.data.siteIssues.entries()) {
|
|
90800
|
+
mirroredCodes.add(issue.code);
|
|
90801
|
+
const id = issue.key ?? `run-issue:${digest([snapshot.runId, snapshot.attemptId, index2, issue])}`;
|
|
90802
|
+
const group = addGroup({
|
|
90803
|
+
id,
|
|
90804
|
+
stableIdentity: issue.key !== null,
|
|
90805
|
+
code: issue.code,
|
|
90806
|
+
source: "cross-page",
|
|
90807
|
+
factorId: null,
|
|
90808
|
+
severity: null,
|
|
90809
|
+
findingType: "info",
|
|
90810
|
+
disposition: ADVISORY_CODES.has(issue.code) ? "advisory" : "informational",
|
|
90811
|
+
...clip2(issue.message),
|
|
90812
|
+
affectedPages: 0,
|
|
90813
|
+
examples: [],
|
|
90814
|
+
examplesTruncated: false
|
|
90815
|
+
});
|
|
90816
|
+
for (const url of [...new Set(issue.affectedUrls)].sort()) addMember(group, pageEvidence(null, url, issue.message));
|
|
90817
|
+
}
|
|
90818
|
+
}
|
|
90819
|
+
if (snapshot.detailsAvailable && snapshot.attemptId) {
|
|
90820
|
+
const scope = and77(
|
|
90821
|
+
eq94(siteCrawlPages.projectId, project.id),
|
|
90822
|
+
eq94(siteCrawlPages.runId, snapshot.runId),
|
|
90823
|
+
eq94(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
90824
|
+
);
|
|
90825
|
+
const size = db.select({ bytes: sql34`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
|
|
90826
|
+
if (size + crossBytes > MAX_EVIDENCE_BYTES) {
|
|
90827
|
+
throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
|
|
90828
|
+
}
|
|
90829
|
+
let after;
|
|
90830
|
+
while (true) {
|
|
90831
|
+
const pages = db.select({
|
|
90832
|
+
nodeKey: siteCrawlPages.nodeKey,
|
|
90833
|
+
url: siteCrawlPages.url,
|
|
90834
|
+
auditScore: siteCrawlPages.auditScore,
|
|
90835
|
+
auditFields: siteCrawlPages.auditFields
|
|
90836
|
+
}).from(siteCrawlPages).where(and77(scope, after === void 0 ? void 0 : gt8(siteCrawlPages.nodeKey, after))).orderBy(asc17(siteCrawlPages.nodeKey)).limit(200).all();
|
|
90837
|
+
for (const page3 of pages) {
|
|
90838
|
+
evidenceHash.update(JSON.stringify(page3));
|
|
90839
|
+
const fields = record(page3.auditFields);
|
|
90840
|
+
if (page3.auditScore !== null || fields?.schemaVersion === "1.0") coverage.auditedPages += 1;
|
|
90841
|
+
if (fields?.schemaVersion !== "1.0") {
|
|
90842
|
+
if (page3.auditScore !== null) coverage.pagesWithoutFindingEvidence += 1;
|
|
90843
|
+
continue;
|
|
90844
|
+
}
|
|
90845
|
+
let pageComplete = Array.isArray(fields.factors) && Array.isArray(fields.criticalDefects);
|
|
90846
|
+
if (!Array.isArray(fields.factors)) coverage.omittedFindings += 1;
|
|
90847
|
+
if (!Array.isArray(fields.criticalDefects)) coverage.omittedFindings += 1;
|
|
90848
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
90849
|
+
for (const rawFactor of Array.isArray(fields.factors) ? fields.factors : []) {
|
|
90850
|
+
const factor = record(rawFactor);
|
|
90851
|
+
if (!factor || typeof factor.id !== "string" || !factor.id || !Array.isArray(factor.findings)) {
|
|
90852
|
+
pageComplete = false;
|
|
90853
|
+
coverage.omittedFindings += 1;
|
|
90854
|
+
continue;
|
|
90855
|
+
}
|
|
90856
|
+
for (const rawFinding of factor.findings) {
|
|
90857
|
+
const parsed = siteCrawlAuditFindingSchema.safeParse(rawFinding);
|
|
90858
|
+
if (!parsed.success) {
|
|
90859
|
+
coverage.omittedFindings += 1;
|
|
90860
|
+
pageComplete = false;
|
|
90861
|
+
continue;
|
|
90862
|
+
}
|
|
90863
|
+
const finding2 = parsed.data;
|
|
90864
|
+
if (factor.applicable === false) continue;
|
|
90865
|
+
if (finding2.type === "found" || finding2.type === "info" && mirroredCodes.has(finding2.code)) continue;
|
|
90866
|
+
const id = `page-issue:${digest(["page-finding-v1", factor.id, finding2.code, finding2.type])}`;
|
|
90867
|
+
if (seen2.has(id)) continue;
|
|
90868
|
+
seen2.add(id);
|
|
90869
|
+
const disposition = finding2.type === "missing" ? "actionable" : finding2.type === "timeout" || finding2.type === "unreachable" ? "unverified" : ADVISORY_CODES.has(finding2.code) ? "advisory" : "informational";
|
|
90870
|
+
const group = addGroup({
|
|
90871
|
+
id,
|
|
90872
|
+
stableIdentity: true,
|
|
90873
|
+
code: finding2.code,
|
|
90874
|
+
source: "page-finding",
|
|
90875
|
+
severity: null,
|
|
90876
|
+
factorId: factor.id,
|
|
90877
|
+
findingType: finding2.type,
|
|
90878
|
+
disposition,
|
|
90879
|
+
...clip2(finding2.message),
|
|
90880
|
+
affectedPages: 0,
|
|
90881
|
+
examples: [],
|
|
90882
|
+
examplesTruncated: false
|
|
90883
|
+
});
|
|
90884
|
+
addMember(group, pageEvidence(page3.nodeKey, page3.url, finding2.message, factor));
|
|
90885
|
+
}
|
|
90886
|
+
}
|
|
90887
|
+
for (const rawDefect of Array.isArray(fields.criticalDefects) ? fields.criticalDefects : []) {
|
|
90888
|
+
const parsed = siteCrawlCriticalDefectSchema.safeParse(rawDefect);
|
|
90889
|
+
if (!parsed.success) {
|
|
90890
|
+
coverage.omittedFindings += 1;
|
|
90891
|
+
pageComplete = false;
|
|
90892
|
+
continue;
|
|
90893
|
+
}
|
|
90894
|
+
const defect = parsed.data;
|
|
90895
|
+
const id = `critical-issue:${digest(["critical-defect-v1", defect.id])}`;
|
|
90896
|
+
if (seen2.has(id)) continue;
|
|
90897
|
+
seen2.add(id);
|
|
90898
|
+
const group = addGroup({
|
|
90899
|
+
id,
|
|
90900
|
+
stableIdentity: true,
|
|
90901
|
+
code: defect.id,
|
|
90902
|
+
source: "critical-defect",
|
|
90903
|
+
factorId: null,
|
|
90904
|
+
severity: defect.severity,
|
|
90905
|
+
findingType: "missing",
|
|
90906
|
+
disposition: "actionable",
|
|
90907
|
+
...clip2(defect.detail),
|
|
90908
|
+
affectedPages: 0,
|
|
90909
|
+
examples: [],
|
|
90910
|
+
examplesTruncated: false
|
|
90911
|
+
});
|
|
90912
|
+
if (defect.severity === "critical") group.severity = "critical";
|
|
90913
|
+
addMember(group, {
|
|
90914
|
+
...pageEvidence(page3.nodeKey, page3.url, defect.detail),
|
|
90915
|
+
recommendations: [defect.recommendation.slice(0, 400)],
|
|
90916
|
+
recommendationsScope: "issue",
|
|
90917
|
+
recommendationsTruncated: defect.recommendation.length > 400
|
|
90918
|
+
});
|
|
90919
|
+
}
|
|
90920
|
+
if (pageComplete) coverage.pagesWithFindingEvidence += 1;
|
|
90921
|
+
else coverage.pagesWithoutFindingEvidence += 1;
|
|
90922
|
+
}
|
|
90923
|
+
if (pages.length < 200) break;
|
|
90924
|
+
after = pages.at(-1).nodeKey;
|
|
90925
|
+
}
|
|
90926
|
+
if (coverage.pagesWithFindingEvidence > 0 || groups.size > 0 && coverage.omittedFindings > 0) {
|
|
90927
|
+
coverage.pageFindings = coverage.pagesWithoutFindingEvidence > 0 || coverage.omittedFindings > 0 ? "partial" : "available";
|
|
90928
|
+
}
|
|
90929
|
+
}
|
|
90930
|
+
const usable = coverage.pageFindings !== "unavailable" || coverage.crossPage === "available" || groups.size > 0;
|
|
90931
|
+
provenance.state = !usable ? "unavailable" : snapshot.complete && coverage.pageFindings === "available" && coverage.crossPage === "available" ? "available" : "partial";
|
|
90932
|
+
const basis = evidenceHash.digest("hex");
|
|
90933
|
+
if (query.cursor && query.cursor !== encodeCursor2(basis, offset)) {
|
|
90934
|
+
throw validationError("Stored audit issue cursor does not match this project, run, attempt, issue, or retained evidence");
|
|
90935
|
+
}
|
|
90936
|
+
const issues = [...groups.values()].sort((a, b) => DISPOSITION_ORDER[a.disposition] - DISPOSITION_ORDER[b.disposition] || (a.severity === "critical" ? 0 : a.severity === "warning" ? 1 : 2) - (b.severity === "critical" ? 0 : b.severity === "warning" ? 1 : 2) || b.affectedPages - a.affectedPages || a.id.localeCompare(b.id));
|
|
90937
|
+
if (issueId !== void 0 && !groups.has(issueId) && usable) throw notFound("Stored audit issue", issueId);
|
|
90938
|
+
provenance.total = usable ? issueId === void 0 ? issues.length : selectedMembers : null;
|
|
90939
|
+
if (offset > 0 && (provenance.total === null || offset >= provenance.total)) throw validationError("Stored audit issue cursor is out of range");
|
|
90940
|
+
const candidates = issueId === void 0 ? issues.slice(offset, offset + limit) : members.slice(0, limit);
|
|
90941
|
+
const responseForLength = (length2) => {
|
|
90942
|
+
const nextCursor = provenance.total !== null && offset + length2 < provenance.total ? encodeCursor2(basis, offset + length2) : null;
|
|
90943
|
+
return issueId === void 0 ? { ...provenance, nextCursor, issues: issues.slice(offset, offset + length2) } : { ...provenance, nextCursor, issueId, pages: members.slice(0, length2) };
|
|
90944
|
+
};
|
|
90945
|
+
let length = candidates.length;
|
|
90946
|
+
while (length > 0 && Buffer.byteLength(JSON.stringify(responseForLength(length), null, 2)) > MAX_RESPONSE_BYTES) length -= 1;
|
|
90947
|
+
if (length === 0 && candidates.length && issueId === void 0) {
|
|
90948
|
+
const first = issues[offset];
|
|
90949
|
+
while (first.examples.length > 1 && Buffer.byteLength(JSON.stringify(responseForLength(1), null, 2)) > MAX_RESPONSE_BYTES) {
|
|
90950
|
+
first.examples.pop();
|
|
90951
|
+
first.examplesTruncated = true;
|
|
90952
|
+
}
|
|
90953
|
+
if (Buffer.byteLength(JSON.stringify(responseForLength(1), null, 2)) <= MAX_RESPONSE_BYTES) length = 1;
|
|
90954
|
+
}
|
|
90955
|
+
const response = responseForLength(length);
|
|
90956
|
+
if (candidates.length && length === 0 || Buffer.byteLength(JSON.stringify(response, null, 2)) > MAX_RESPONSE_BYTES) {
|
|
90957
|
+
throw validationError("Stored audit issue exceeds the 16 KiB response budget");
|
|
90958
|
+
}
|
|
90959
|
+
return response;
|
|
90960
|
+
}
|
|
90961
|
+
|
|
90962
|
+
// ../api-routes/src/technical-aeo.ts
|
|
90618
90963
|
var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
90619
90964
|
...SiteCrawlFetchedStates,
|
|
90620
90965
|
// Snapshots published before the full-crawl contract used this aggregate
|
|
@@ -90683,7 +91028,7 @@ function parseBoolean2(value) {
|
|
|
90683
91028
|
if (value === false || value === "false" || value === "0") return false;
|
|
90684
91029
|
return null;
|
|
90685
91030
|
}
|
|
90686
|
-
function
|
|
91031
|
+
function encodeCursor3(offset) {
|
|
90687
91032
|
return Buffer.from(JSON.stringify({ offset })).toString("base64url");
|
|
90688
91033
|
}
|
|
90689
91034
|
function decodeCursor(value) {
|
|
@@ -90850,13 +91195,13 @@ function parseLinkKind(value) {
|
|
|
90850
91195
|
return allowed.data;
|
|
90851
91196
|
}
|
|
90852
91197
|
function linkKindFilter(linkKind) {
|
|
90853
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
90854
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
91198
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq95(siteCrawlEdges.isTemplate, false);
|
|
91199
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq95(siteCrawlEdges.isTemplate, true);
|
|
90855
91200
|
return void 0;
|
|
90856
91201
|
}
|
|
90857
91202
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
90858
91203
|
var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
|
|
90859
|
-
var PAGE_CHANGE_PREDICATE =
|
|
91204
|
+
var PAGE_CHANGE_PREDICATE = sql35`(
|
|
90860
91205
|
current.url IS NOT previous.url
|
|
90861
91206
|
OR current.final_url IS NOT previous.final_url
|
|
90862
91207
|
OR current.path IS NOT previous.path
|
|
@@ -90878,7 +91223,7 @@ var PAGE_CHANGE_PREDICATE = sql34`(
|
|
|
90878
91223
|
OR current.link_score_raw IS NOT previous.link_score_raw
|
|
90879
91224
|
OR current.link_score_normalized IS NOT previous.link_score_normalized
|
|
90880
91225
|
)`;
|
|
90881
|
-
var LINK_CHANGE_PREDICATE =
|
|
91226
|
+
var LINK_CHANGE_PREDICATE = sql35`(
|
|
90882
91227
|
current.source_node_key IS NOT previous.source_node_key
|
|
90883
91228
|
OR current.source_url IS NOT previous.source_url
|
|
90884
91229
|
OR current.target_node_key IS NOT previous.target_node_key
|
|
@@ -90955,20 +91300,20 @@ function changedFields(before, after, fields) {
|
|
|
90955
91300
|
}
|
|
90956
91301
|
async function technicalAeoRoutes(app, opts) {
|
|
90957
91302
|
const visibleCrawlFilters = (projectId) => [
|
|
90958
|
-
|
|
90959
|
-
|
|
90960
|
-
|
|
91303
|
+
eq95(siteCrawlSnapshots.projectId, projectId),
|
|
91304
|
+
eq95(runs.projectId, projectId),
|
|
91305
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
90961
91306
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
90962
91307
|
notProbeRun()
|
|
90963
91308
|
];
|
|
90964
91309
|
const availableScanDatesFor = (projectId, requestedDate) => {
|
|
90965
91310
|
if (!requestedDate) return void 0;
|
|
90966
|
-
const date =
|
|
91311
|
+
const date = sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
90967
91312
|
const filters = visibleCrawlFilters(projectId);
|
|
90968
|
-
const allDates =
|
|
90969
|
-
const matchingDates =
|
|
90970
|
-
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90971
|
-
const countDates = (where) => app.db.select({ value:
|
|
91313
|
+
const allDates = and78(...filters);
|
|
91314
|
+
const matchingDates = and78(...filters, eq95(sql35`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
91315
|
+
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc43(date)).limit(10).all().map((row) => row.date);
|
|
91316
|
+
const countDates = (where) => app.db.select({ value: sql35`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
|
|
90972
91317
|
return {
|
|
90973
91318
|
recentDates: readDates(allDates),
|
|
90974
91319
|
totalDates: countDates(allDates),
|
|
@@ -90984,9 +91329,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90984
91329
|
nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
|
|
90985
91330
|
filters.push(gte24(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
90986
91331
|
}
|
|
90987
|
-
const selectionFilters = scope.scoredOnly ? [...filters,
|
|
90988
|
-
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90989
|
-
desc43(
|
|
91332
|
+
const selectionFilters = scope.scoredOnly ? [...filters, sql35`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
91333
|
+
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(...selectionFilters, runId ? eq95(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
|
|
91334
|
+
desc43(sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
90990
91335
|
desc43(siteCrawlSnapshots.complete),
|
|
90991
91336
|
desc43(siteCrawlSnapshots.pagesFetched),
|
|
90992
91337
|
desc43(siteCrawlSnapshots.createdAt),
|
|
@@ -90996,18 +91341,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90996
91341
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
90997
91342
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
90998
91343
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
90999
|
-
const sameDate =
|
|
91344
|
+
const sameDate = and78(
|
|
91000
91345
|
...filters,
|
|
91001
91346
|
gte24(siteCrawlSnapshots.createdAt, date),
|
|
91002
91347
|
lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
91003
91348
|
);
|
|
91004
|
-
const sameDateRunCount = app.db.select({ value: count7() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
91349
|
+
const sameDateRunCount = app.db.select({ value: count7() }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
91005
91350
|
const candidates = app.db.select({
|
|
91006
91351
|
runId: siteCrawlSnapshots.runId,
|
|
91007
91352
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
91008
91353
|
complete: siteCrawlSnapshots.complete,
|
|
91009
91354
|
pages: siteCrawlSnapshots.pagesFetched
|
|
91010
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
91355
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
91011
91356
|
desc43(siteCrawlSnapshots.complete),
|
|
91012
91357
|
desc43(siteCrawlSnapshots.pagesFetched),
|
|
91013
91358
|
desc43(siteCrawlSnapshots.createdAt),
|
|
@@ -91025,16 +91370,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91025
91370
|
};
|
|
91026
91371
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
91027
91372
|
const filters = [
|
|
91028
|
-
|
|
91029
|
-
|
|
91030
|
-
|
|
91373
|
+
eq95(siteAuditSnapshots.projectId, projectId),
|
|
91374
|
+
eq95(runs.projectId, projectId),
|
|
91375
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
91031
91376
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
91032
91377
|
notProbeRun(),
|
|
91033
91378
|
...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
91034
91379
|
];
|
|
91035
|
-
const complete =
|
|
91036
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
91037
|
-
desc43(
|
|
91380
|
+
const complete = sql35`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
91381
|
+
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(...filters, pinnedRunId ? eq95(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
|
|
91382
|
+
desc43(sql35`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
91038
91383
|
desc43(complete),
|
|
91039
91384
|
desc43(siteAuditSnapshots.pagesAudited),
|
|
91040
91385
|
desc43(siteAuditSnapshots.createdAt),
|
|
@@ -91054,18 +91399,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91054
91399
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
91055
91400
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
91056
91401
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
91057
|
-
const sameDate =
|
|
91402
|
+
const sameDate = and78(
|
|
91058
91403
|
...filters,
|
|
91059
91404
|
gte24(siteAuditSnapshots.createdAt, date),
|
|
91060
91405
|
lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
91061
91406
|
);
|
|
91062
|
-
const sameDateRunCount = app.db.select({ value: count7() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
91407
|
+
const sameDateRunCount = app.db.select({ value: count7() }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
91063
91408
|
const candidates = app.db.select({
|
|
91064
91409
|
runId: siteAuditSnapshots.runId,
|
|
91065
91410
|
createdAt: siteAuditSnapshots.createdAt,
|
|
91066
91411
|
complete,
|
|
91067
91412
|
pages: siteAuditSnapshots.pagesAudited
|
|
91068
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
91413
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
91069
91414
|
desc43(complete),
|
|
91070
91415
|
desc43(siteAuditSnapshots.pagesAudited),
|
|
91071
91416
|
desc43(siteAuditSnapshots.createdAt),
|
|
@@ -91084,9 +91429,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91084
91429
|
};
|
|
91085
91430
|
const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
|
|
91086
91431
|
const crawlReasonGroups = (where) => {
|
|
91087
|
-
const canonicalOther =
|
|
91432
|
+
const canonicalOther = sql35`case when ${siteCrawlPages.canonicalNodeKey} is not null
|
|
91088
91433
|
and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
|
|
91089
|
-
const canonicalReason =
|
|
91434
|
+
const canonicalReason = sql35`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
|
|
91090
91435
|
where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
|
|
91091
91436
|
const groups = app.db.select({
|
|
91092
91437
|
fetchState: siteCrawlPages.fetchState,
|
|
@@ -91095,7 +91440,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91095
91440
|
canonicalOther,
|
|
91096
91441
|
canonicalReason,
|
|
91097
91442
|
pages: count7(),
|
|
91098
|
-
exampleUrl:
|
|
91443
|
+
exampleUrl: sql35`min(${siteCrawlPages.url})`
|
|
91099
91444
|
}).from(siteCrawlPages).where(where).groupBy(
|
|
91100
91445
|
siteCrawlPages.fetchState,
|
|
91101
91446
|
siteCrawlPages.indexabilityState,
|
|
@@ -91131,10 +91476,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91131
91476
|
};
|
|
91132
91477
|
const inventorySummaryFor = (scope) => {
|
|
91133
91478
|
if (!scope) return null;
|
|
91134
|
-
const groups = crawlReasonGroups(
|
|
91135
|
-
|
|
91136
|
-
|
|
91137
|
-
|
|
91479
|
+
const groups = crawlReasonGroups(and78(
|
|
91480
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
91481
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
91482
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId)
|
|
91138
91483
|
));
|
|
91139
91484
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
91140
91485
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
@@ -91154,33 +91499,33 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91154
91499
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
91155
91500
|
const pageInScope = (scope, selector) => {
|
|
91156
91501
|
if (!selector.nodeKey && !selector.url) return null;
|
|
91157
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
91158
|
-
|
|
91159
|
-
|
|
91160
|
-
|
|
91161
|
-
selector.nodeKey ?
|
|
91162
|
-
)).orderBy(
|
|
91163
|
-
};
|
|
91164
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
91165
|
-
|
|
91166
|
-
|
|
91167
|
-
|
|
91168
|
-
|
|
91169
|
-
)).orderBy(
|
|
91170
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
91171
|
-
|
|
91172
|
-
|
|
91173
|
-
|
|
91502
|
+
return app.db.select().from(siteCrawlPages).where(and78(
|
|
91503
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
91504
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
91505
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
91506
|
+
selector.nodeKey ? eq95(siteCrawlPages.nodeKey, selector.nodeKey) : eq95(siteCrawlPages.url, selector.url)
|
|
91507
|
+
)).orderBy(asc18(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
91508
|
+
};
|
|
91509
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and78(
|
|
91510
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
91511
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
91512
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
91513
|
+
eq95(siteCrawlPages.depth, 0)
|
|
91514
|
+
)).orderBy(asc18(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
91515
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and78(
|
|
91516
|
+
eq95(runs.id, runId),
|
|
91517
|
+
eq95(runs.projectId, projectId),
|
|
91518
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
91174
91519
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
91175
91520
|
notProbeRun()
|
|
91176
91521
|
)).limit(1).get());
|
|
91177
91522
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
91178
91523
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
91179
91524
|
};
|
|
91180
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
91181
|
-
|
|
91182
|
-
|
|
91183
|
-
|
|
91525
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(
|
|
91526
|
+
eq95(siteAuditSnapshots.projectId, projectId),
|
|
91527
|
+
eq95(runs.projectId, projectId),
|
|
91528
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
91184
91529
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
91185
91530
|
notProbeRun()
|
|
91186
91531
|
)).limit(1).get());
|
|
@@ -91282,14 +91627,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91282
91627
|
};
|
|
91283
91628
|
}
|
|
91284
91629
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
91285
|
-
const conds = [
|
|
91286
|
-
if (statusFilter) conds.push(
|
|
91287
|
-
const where =
|
|
91630
|
+
const conds = [eq95(siteAuditPages.projectId, project.id), eq95(siteAuditPages.runId, latest.snap.runId)];
|
|
91631
|
+
if (statusFilter) conds.push(eq95(siteAuditPages.status, statusFilter));
|
|
91632
|
+
const where = and78(...conds);
|
|
91288
91633
|
const totalRow = app.db.select({ value: count7() }).from(siteAuditPages).where(where).get();
|
|
91289
91634
|
const total = totalRow?.value ?? 0;
|
|
91290
91635
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
91291
91636
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
91292
|
-
const orderBy = request.query.sort === "score-desc" ? desc43(siteAuditPages.overallScore) : request.query.sort === "url" ?
|
|
91637
|
+
const orderBy = request.query.sort === "score-desc" ? desc43(siteAuditPages.overallScore) : request.query.sort === "url" ? asc18(siteAuditPages.url) : asc18(siteAuditPages.overallScore);
|
|
91293
91638
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
91294
91639
|
const pages = rows.map((row) => ({
|
|
91295
91640
|
url: row.url,
|
|
@@ -91308,10 +91653,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91308
91653
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
91309
91654
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
91310
91655
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
91311
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
91312
|
-
|
|
91313
|
-
|
|
91314
|
-
|
|
91656
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq95(siteAuditSnapshots.runId, runs.id)).where(and78(
|
|
91657
|
+
eq95(siteAuditSnapshots.projectId, project.id),
|
|
91658
|
+
eq95(runs.projectId, project.id),
|
|
91659
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
91315
91660
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
91316
91661
|
notProbeRun()
|
|
91317
91662
|
)).orderBy(desc43(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
@@ -91414,10 +91759,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91414
91759
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
91415
91760
|
snapshot.rootUrl
|
|
91416
91761
|
)?.nodeKey ?? null;
|
|
91417
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
91418
|
-
|
|
91419
|
-
|
|
91420
|
-
|
|
91762
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and78(
|
|
91763
|
+
eq95(siteCrawlGraphLayouts.projectId, project.id),
|
|
91764
|
+
eq95(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
91765
|
+
eq95(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
91421
91766
|
)).limit(1).get();
|
|
91422
91767
|
if (!persistedLayout) {
|
|
91423
91768
|
return {
|
|
@@ -91470,9 +91815,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91470
91815
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
91471
91816
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
91472
91817
|
const graphScope = [
|
|
91473
|
-
|
|
91474
|
-
|
|
91475
|
-
|
|
91818
|
+
eq95(siteCrawlGraphNodes.projectId, project.id),
|
|
91819
|
+
eq95(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
91820
|
+
eq95(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
91476
91821
|
];
|
|
91477
91822
|
const nodeRows = app.db.select({
|
|
91478
91823
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -91491,18 +91836,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91491
91836
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
91492
91837
|
x: siteCrawlGraphNodes.x,
|
|
91493
91838
|
y: siteCrawlGraphNodes.y
|
|
91494
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
91495
|
-
|
|
91496
|
-
|
|
91497
|
-
|
|
91498
|
-
|
|
91499
|
-
)).where(
|
|
91839
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and78(
|
|
91840
|
+
eq95(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
91841
|
+
eq95(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
91842
|
+
eq95(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
91843
|
+
eq95(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
91844
|
+
)).where(and78(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc18(siteCrawlGraphNodes.sampleRank)).all();
|
|
91500
91845
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
91501
91846
|
...row,
|
|
91502
91847
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
91503
91848
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
91504
91849
|
}));
|
|
91505
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
91850
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq95(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
91506
91851
|
const edges = app.db.select({
|
|
91507
91852
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
91508
91853
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -91510,25 +91855,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91510
91855
|
followable: siteCrawlGraphEdges.followable,
|
|
91511
91856
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
91512
91857
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
91513
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
91514
|
-
|
|
91515
|
-
|
|
91516
|
-
|
|
91517
|
-
|
|
91858
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and78(
|
|
91859
|
+
eq95(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
91860
|
+
eq95(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
91861
|
+
eq95(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
91862
|
+
eq95(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
91518
91863
|
lt13(graphSourceNode.sampleRank, maxNodes)
|
|
91519
|
-
)).innerJoin(graphTargetNode,
|
|
91520
|
-
|
|
91521
|
-
|
|
91522
|
-
|
|
91523
|
-
|
|
91864
|
+
)).innerJoin(graphTargetNode, and78(
|
|
91865
|
+
eq95(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
91866
|
+
eq95(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
91867
|
+
eq95(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
91868
|
+
eq95(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
91524
91869
|
lt13(graphTargetNode.sampleRank, maxNodes)
|
|
91525
|
-
)).where(
|
|
91526
|
-
|
|
91527
|
-
|
|
91528
|
-
|
|
91870
|
+
)).where(and78(
|
|
91871
|
+
eq95(siteCrawlGraphEdges.projectId, project.id),
|
|
91872
|
+
eq95(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
91873
|
+
eq95(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
91529
91874
|
lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
91530
91875
|
graphLinkKindFilter
|
|
91531
|
-
)).orderBy(
|
|
91876
|
+
)).orderBy(asc18(siteCrawlGraphEdges.sampleRank)).all();
|
|
91532
91877
|
const totalNodes = persistedLayout.totalNodes;
|
|
91533
91878
|
const totalEdges = persistedLayout.totalEdges;
|
|
91534
91879
|
const totalTemplateEdges = persistedLayout.totalTemplateEdges;
|
|
@@ -91620,17 +91965,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91620
91965
|
let frontier = [focus.nodeKey];
|
|
91621
91966
|
let queryTruncated = false;
|
|
91622
91967
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
91623
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
91624
|
-
|
|
91625
|
-
|
|
91626
|
-
|
|
91627
|
-
|
|
91968
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and78(
|
|
91969
|
+
eq95(siteCrawlEdges.projectId, scope.projectId),
|
|
91970
|
+
eq95(siteCrawlEdges.runId, scope.runId),
|
|
91971
|
+
eq95(siteCrawlEdges.attemptId, scope.attemptId),
|
|
91972
|
+
eq95(siteCrawlEdges.internal, true),
|
|
91628
91973
|
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
91629
91974
|
or22(
|
|
91630
91975
|
inArray38(siteCrawlEdges.sourceNodeKey, frontier),
|
|
91631
91976
|
inArray38(siteCrawlEdges.targetNodeKey, frontier)
|
|
91632
91977
|
)
|
|
91633
|
-
)).orderBy(
|
|
91978
|
+
)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
91634
91979
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
91635
91980
|
const next = /* @__PURE__ */ new Set();
|
|
91636
91981
|
for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
|
|
@@ -91661,19 +92006,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91661
92006
|
frontier = [...next].sort();
|
|
91662
92007
|
}
|
|
91663
92008
|
const nodeKeys = [...distances.keys()];
|
|
91664
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
91665
|
-
|
|
91666
|
-
|
|
91667
|
-
|
|
92009
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and78(
|
|
92010
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
92011
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
92012
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
91668
92013
|
inArray38(siteCrawlPages.nodeKey, nodeKeys)
|
|
91669
92014
|
)).all();
|
|
91670
92015
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
91671
92016
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
91672
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
91673
|
-
|
|
91674
|
-
|
|
91675
|
-
|
|
91676
|
-
|
|
92017
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and78(
|
|
92018
|
+
eq95(siteCrawlEdges.projectId, scope.projectId),
|
|
92019
|
+
eq95(siteCrawlEdges.runId, scope.runId),
|
|
92020
|
+
eq95(siteCrawlEdges.attemptId, scope.attemptId),
|
|
92021
|
+
eq95(siteCrawlEdges.internal, true),
|
|
91677
92022
|
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
91678
92023
|
inArray38(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
91679
92024
|
inArray38(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
@@ -91769,22 +92114,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91769
92114
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
91770
92115
|
let truncated = false;
|
|
91771
92116
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
91772
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
91773
|
-
|
|
91774
|
-
|
|
91775
|
-
|
|
91776
|
-
|
|
91777
|
-
|
|
91778
|
-
|
|
92117
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and78(
|
|
92118
|
+
eq95(siteCrawlEdges.projectId, scope.projectId),
|
|
92119
|
+
eq95(siteCrawlEdges.runId, scope.runId),
|
|
92120
|
+
eq95(siteCrawlEdges.attemptId, scope.attemptId),
|
|
92121
|
+
eq95(siteCrawlEdges.internal, true),
|
|
92122
|
+
eq95(siteCrawlEdges.followable, true),
|
|
92123
|
+
eq95(siteCrawlEdges.relation, "anchor"),
|
|
91779
92124
|
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
91780
92125
|
inArray38(siteCrawlEdges.sourceNodeKey, frontier)
|
|
91781
|
-
)).orderBy(
|
|
92126
|
+
)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
91782
92127
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
91783
92128
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
91784
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
91785
|
-
|
|
91786
|
-
|
|
91787
|
-
|
|
92129
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and78(
|
|
92130
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
92131
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
92132
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
91788
92133
|
inArray38(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
91789
92134
|
)).all().map((row) => row.nodeKey));
|
|
91790
92135
|
const next = /* @__PURE__ */ new Set();
|
|
@@ -91826,10 +92171,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91826
92171
|
pathEdges.unshift(step.edge);
|
|
91827
92172
|
pathKeys.unshift(step.nodeKey);
|
|
91828
92173
|
}
|
|
91829
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
91830
|
-
|
|
91831
|
-
|
|
91832
|
-
|
|
92174
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and78(
|
|
92175
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
92176
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
92177
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
91833
92178
|
inArray38(siteCrawlPages.nodeKey, pathKeys)
|
|
91834
92179
|
)).all();
|
|
91835
92180
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
@@ -91858,21 +92203,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91858
92203
|
throw validationError("change must be all, added, removed, or changed");
|
|
91859
92204
|
}
|
|
91860
92205
|
const completeSnapshotFilters = [
|
|
91861
|
-
|
|
91862
|
-
|
|
91863
|
-
|
|
91864
|
-
|
|
91865
|
-
|
|
92206
|
+
eq95(siteCrawlSnapshots.projectId, project.id),
|
|
92207
|
+
eq95(runs.projectId, project.id),
|
|
92208
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92209
|
+
eq95(runs.status, RunStatuses.completed),
|
|
92210
|
+
eq95(siteCrawlSnapshots.complete, true),
|
|
91866
92211
|
notProbeRun()
|
|
91867
92212
|
];
|
|
91868
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
92213
|
+
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(...completeSnapshotFilters)).orderBy(desc43(siteCrawlSnapshots.createdAt), desc43(siteCrawlSnapshots.runId)).limit(1).get();
|
|
91869
92214
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
91870
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
92215
|
+
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(
|
|
91871
92216
|
...completeSnapshotFilters,
|
|
91872
92217
|
or22(
|
|
91873
92218
|
lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
91874
|
-
|
|
91875
|
-
|
|
92219
|
+
and78(
|
|
92220
|
+
eq95(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
91876
92221
|
lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
91877
92222
|
)
|
|
91878
92223
|
)
|
|
@@ -91960,9 +92305,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91960
92305
|
const summary4 = { pages: zero2(), links: zero2() };
|
|
91961
92306
|
if (scopeFilter !== "links") {
|
|
91962
92307
|
if (includeAdded || includeChanged) {
|
|
91963
|
-
const addedExpression = includeAdded ?
|
|
91964
|
-
const changedExpression = includeChanged ?
|
|
91965
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
92308
|
+
const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
|
|
92309
|
+
const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
|
|
92310
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
|
|
91966
92311
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
91967
92312
|
FROM site_crawl_pages AS current
|
|
91968
92313
|
LEFT JOIN site_crawl_pages AS previous
|
|
@@ -91978,7 +92323,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91978
92323
|
summary4.pages.changed = Number(counts2.changed);
|
|
91979
92324
|
}
|
|
91980
92325
|
if (includeRemoved) {
|
|
91981
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
92326
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql35`
|
|
91982
92327
|
SELECT COUNT(*) AS removed
|
|
91983
92328
|
FROM site_crawl_pages AS previous
|
|
91984
92329
|
LEFT JOIN site_crawl_pages AS current
|
|
@@ -91996,9 +92341,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91996
92341
|
}
|
|
91997
92342
|
if (scopeFilter !== "pages") {
|
|
91998
92343
|
if (includeAdded || includeChanged) {
|
|
91999
|
-
const addedExpression = includeAdded ?
|
|
92000
|
-
const changedExpression = includeChanged ?
|
|
92001
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
92344
|
+
const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
|
|
92345
|
+
const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
|
|
92346
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
|
|
92002
92347
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
92003
92348
|
FROM site_crawl_edges AS current
|
|
92004
92349
|
LEFT JOIN site_crawl_edges AS previous
|
|
@@ -92016,7 +92361,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92016
92361
|
summary4.links.changed = Number(counts2.changed);
|
|
92017
92362
|
}
|
|
92018
92363
|
if (includeRemoved) {
|
|
92019
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
92364
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql35`
|
|
92020
92365
|
SELECT COUNT(*) AS removed
|
|
92021
92366
|
FROM site_crawl_edges AS previous
|
|
92022
92367
|
LEFT JOIN site_crawl_edges AS current
|
|
@@ -92046,48 +92391,48 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92046
92391
|
const includeRemoved = changeFilter === "all" || changeFilter === "removed";
|
|
92047
92392
|
const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
|
|
92048
92393
|
const keyColumn = entity === "page" ? "node_key" : "edge_key";
|
|
92049
|
-
const internalCurrent = entity === "link" ?
|
|
92050
|
-
const internalPreviousJoin = entity === "link" ?
|
|
92051
|
-
const internalPrevious = entity === "link" ?
|
|
92052
|
-
const internalCurrentJoin = entity === "link" ?
|
|
92394
|
+
const internalCurrent = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
|
|
92395
|
+
const internalPreviousJoin = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
|
|
92396
|
+
const internalPrevious = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
|
|
92397
|
+
const internalCurrentJoin = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
|
|
92053
92398
|
const difference2 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
|
|
92054
|
-
const afterKind = changeFilter === "added" ?
|
|
92055
|
-
const afterRows = includeAfter ? app.db.all(
|
|
92399
|
+
const afterKind = changeFilter === "added" ? sql35`previous.id IS NULL` : changeFilter === "changed" ? sql35`previous.id IS NOT NULL AND ${difference2}` : sql35`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
|
|
92400
|
+
const afterRows = includeAfter ? app.db.all(sql35`
|
|
92056
92401
|
SELECT ${entity} AS entity,
|
|
92057
92402
|
CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
|
|
92058
|
-
current.${
|
|
92059
|
-
FROM ${
|
|
92060
|
-
LEFT JOIN ${
|
|
92403
|
+
current.${sql35.raw(keyColumn)} AS key
|
|
92404
|
+
FROM ${sql35.raw(table)} AS current
|
|
92405
|
+
LEFT JOIN ${sql35.raw(table)} AS previous
|
|
92061
92406
|
ON previous.project_id = ${beforeScope.projectId}
|
|
92062
92407
|
AND previous.run_id = ${beforeScope.runId}
|
|
92063
92408
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
92064
92409
|
${internalPreviousJoin}
|
|
92065
|
-
AND previous.${
|
|
92410
|
+
AND previous.${sql35.raw(keyColumn)} = current.${sql35.raw(keyColumn)}
|
|
92066
92411
|
WHERE current.project_id = ${afterScope.projectId}
|
|
92067
92412
|
AND current.run_id = ${afterScope.runId}
|
|
92068
92413
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
92069
92414
|
${internalCurrent}
|
|
92070
|
-
AND current.${
|
|
92415
|
+
AND current.${sql35.raw(keyColumn)} > ${afterKey}
|
|
92071
92416
|
AND (${afterKind})
|
|
92072
|
-
ORDER BY current.${
|
|
92417
|
+
ORDER BY current.${sql35.raw(keyColumn)} ASC
|
|
92073
92418
|
LIMIT ${wanted2}
|
|
92074
92419
|
`) : [];
|
|
92075
|
-
const removedRows = includeRemoved ? app.db.all(
|
|
92076
|
-
SELECT ${entity} AS entity, 'removed' AS change, previous.${
|
|
92077
|
-
FROM ${
|
|
92078
|
-
LEFT JOIN ${
|
|
92420
|
+
const removedRows = includeRemoved ? app.db.all(sql35`
|
|
92421
|
+
SELECT ${entity} AS entity, 'removed' AS change, previous.${sql35.raw(keyColumn)} AS key
|
|
92422
|
+
FROM ${sql35.raw(table)} AS previous
|
|
92423
|
+
LEFT JOIN ${sql35.raw(table)} AS current
|
|
92079
92424
|
ON current.project_id = ${afterScope.projectId}
|
|
92080
92425
|
AND current.run_id = ${afterScope.runId}
|
|
92081
92426
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
92082
92427
|
${internalCurrentJoin}
|
|
92083
|
-
AND current.${
|
|
92428
|
+
AND current.${sql35.raw(keyColumn)} = previous.${sql35.raw(keyColumn)}
|
|
92084
92429
|
WHERE previous.project_id = ${beforeScope.projectId}
|
|
92085
92430
|
AND previous.run_id = ${beforeScope.runId}
|
|
92086
92431
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
92087
92432
|
${internalPrevious}
|
|
92088
|
-
AND previous.${
|
|
92433
|
+
AND previous.${sql35.raw(keyColumn)} > ${afterKey}
|
|
92089
92434
|
AND current.id IS NULL
|
|
92090
|
-
ORDER BY previous.${
|
|
92435
|
+
ORDER BY previous.${sql35.raw(keyColumn)} ASC
|
|
92091
92436
|
LIMIT ${wanted2}
|
|
92092
92437
|
`) : [];
|
|
92093
92438
|
const merged = [];
|
|
@@ -92117,17 +92462,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92117
92462
|
const visibleKeys = keyRows.slice(0, limit);
|
|
92118
92463
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
92119
92464
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
92120
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
92121
|
-
|
|
92122
|
-
|
|
92123
|
-
|
|
92465
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and78(
|
|
92466
|
+
eq95(siteCrawlPages.projectId, scope.projectId),
|
|
92467
|
+
eq95(siteCrawlPages.runId, scope.runId),
|
|
92468
|
+
eq95(siteCrawlPages.attemptId, scope.attemptId),
|
|
92124
92469
|
inArray38(siteCrawlPages.nodeKey, pageKeys)
|
|
92125
92470
|
)).all();
|
|
92126
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
92127
|
-
|
|
92128
|
-
|
|
92129
|
-
|
|
92130
|
-
|
|
92471
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and78(
|
|
92472
|
+
eq95(siteCrawlEdges.projectId, scope.projectId),
|
|
92473
|
+
eq95(siteCrawlEdges.runId, scope.runId),
|
|
92474
|
+
eq95(siteCrawlEdges.attemptId, scope.attemptId),
|
|
92475
|
+
eq95(siteCrawlEdges.internal, true),
|
|
92131
92476
|
inArray38(siteCrawlEdges.edgeKey, linkKeys)
|
|
92132
92477
|
)).all();
|
|
92133
92478
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -92189,6 +92534,26 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92189
92534
|
changes
|
|
92190
92535
|
};
|
|
92191
92536
|
});
|
|
92537
|
+
app.get(
|
|
92538
|
+
"/projects/:name/technical-aeo/issues",
|
|
92539
|
+
async (request) => {
|
|
92540
|
+
const project = resolveProject(app.db, request.params.name);
|
|
92541
|
+
const query = parseSiteAuditIssueQuery(request.query);
|
|
92542
|
+
const target = resolveCrawl(project.id, query.runId);
|
|
92543
|
+
if (!target) assertKnownAuditRun(project.id, query.runId);
|
|
92544
|
+
return readSiteAuditIssues({ db: app.db, project, target, query });
|
|
92545
|
+
}
|
|
92546
|
+
);
|
|
92547
|
+
app.get(
|
|
92548
|
+
"/projects/:name/technical-aeo/issues/:issueId/pages",
|
|
92549
|
+
async (request) => {
|
|
92550
|
+
const project = resolveProject(app.db, request.params.name);
|
|
92551
|
+
const query = parseSiteAuditIssueQuery(request.query);
|
|
92552
|
+
const target = resolveCrawl(project.id, query.runId);
|
|
92553
|
+
if (!target) assertKnownAuditRun(project.id, query.runId);
|
|
92554
|
+
return readSiteAuditIssues({ db: app.db, project, target, query, issueId: request.params.issueId });
|
|
92555
|
+
}
|
|
92556
|
+
);
|
|
92192
92557
|
app.get("/projects/:name/technical-aeo/crawl/pages/audit", async (request) => {
|
|
92193
92558
|
const project = resolveProject(app.db, request.params.name);
|
|
92194
92559
|
const scalarQueryString = (value, name) => {
|
|
@@ -92281,16 +92646,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92281
92646
|
}
|
|
92282
92647
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
92283
92648
|
const filters = [
|
|
92284
|
-
|
|
92285
|
-
|
|
92286
|
-
|
|
92649
|
+
eq95(siteCrawlPages.projectId, project.id),
|
|
92650
|
+
eq95(siteCrawlPages.runId, snapshot.runId),
|
|
92651
|
+
eq95(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
92287
92652
|
];
|
|
92288
|
-
if (request.query.nodeKey) filters.push(
|
|
92653
|
+
if (request.query.nodeKey) filters.push(eq95(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
92289
92654
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
92290
|
-
if (inventoryEligible != null) filters.push(
|
|
92291
|
-
if (request.query.fetchState) filters.push(
|
|
92292
|
-
if (request.query.indexabilityState) filters.push(
|
|
92293
|
-
if (request.query.auditState) filters.push(
|
|
92655
|
+
if (inventoryEligible != null) filters.push(eq95(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
92656
|
+
if (request.query.fetchState) filters.push(eq95(siteCrawlPages.fetchState, request.query.fetchState));
|
|
92657
|
+
if (request.query.indexabilityState) filters.push(eq95(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
92658
|
+
if (request.query.auditState) filters.push(eq95(siteCrawlPages.auditState, request.query.auditState));
|
|
92294
92659
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
92295
92660
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
92296
92661
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
@@ -92308,17 +92673,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92308
92673
|
sort
|
|
92309
92674
|
})).digest("base64url");
|
|
92310
92675
|
const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
|
|
92311
|
-
const orderBy = sort === "score-desc" ? [desc43(siteCrawlPages.auditScore),
|
|
92676
|
+
const orderBy = sort === "score-desc" ? [desc43(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc18(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)] : sort === "path" ? [asc18(siteCrawlPages.path), asc18(siteCrawlPages.nodeKey)] : [asc18(siteCrawlPages.url), asc18(siteCrawlPages.nodeKey)];
|
|
92312
92677
|
let healthStateFilter = null;
|
|
92313
92678
|
if (healthState) {
|
|
92314
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
92315
|
-
|
|
92316
|
-
|
|
92317
|
-
|
|
92679
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and78(
|
|
92680
|
+
eq95(siteCrawlPages.projectId, project.id),
|
|
92681
|
+
eq95(siteCrawlPages.runId, snapshot.runId),
|
|
92682
|
+
eq95(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
92318
92683
|
isNull18(siteCrawlPages.healthState)
|
|
92319
92684
|
)).limit(1).get();
|
|
92320
92685
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
92321
|
-
if (healthStateFilter === "applied") filters.push(
|
|
92686
|
+
if (healthStateFilter === "applied") filters.push(eq95(siteCrawlPages.healthState, healthState));
|
|
92322
92687
|
}
|
|
92323
92688
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
92324
92689
|
return {
|
|
@@ -92335,7 +92700,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92335
92700
|
pages: []
|
|
92336
92701
|
};
|
|
92337
92702
|
}
|
|
92338
|
-
const where =
|
|
92703
|
+
const where = and78(...filters);
|
|
92339
92704
|
const total = app.db.select({ value: count7() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
92340
92705
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
92341
92706
|
const nextOffset = offset + rows.length;
|
|
@@ -92370,10 +92735,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92370
92735
|
url: siteCrawlPages.url,
|
|
92371
92736
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
92372
92737
|
fetchState: siteCrawlPages.fetchState
|
|
92373
|
-
}).from(siteCrawlPages).where(
|
|
92374
|
-
|
|
92375
|
-
|
|
92376
|
-
|
|
92738
|
+
}).from(siteCrawlPages).where(and78(
|
|
92739
|
+
eq95(siteCrawlPages.projectId, project.id),
|
|
92740
|
+
eq95(siteCrawlPages.runId, snapshot.runId),
|
|
92741
|
+
eq95(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
92377
92742
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
92378
92743
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
92379
92744
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -92410,7 +92775,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92410
92775
|
runId: snapshot.runId,
|
|
92411
92776
|
...completenessOf(target),
|
|
92412
92777
|
parentPath,
|
|
92413
|
-
nextCursor: hasMore ?
|
|
92778
|
+
nextCursor: hasMore ? encodeCursor3(offset + visible.length) : null,
|
|
92414
92779
|
children: visible.map(([path8, child]) => ({
|
|
92415
92780
|
path: path8,
|
|
92416
92781
|
url: child.url,
|
|
@@ -92455,21 +92820,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92455
92820
|
};
|
|
92456
92821
|
}
|
|
92457
92822
|
const filters = [
|
|
92458
|
-
|
|
92459
|
-
|
|
92460
|
-
|
|
92461
|
-
|
|
92823
|
+
eq95(siteCrawlEdges.projectId, project.id),
|
|
92824
|
+
eq95(siteCrawlEdges.runId, snapshot.runId),
|
|
92825
|
+
eq95(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
92826
|
+
eq95(siteCrawlEdges.internal, true),
|
|
92462
92827
|
linkKindFilter(linkKind)
|
|
92463
92828
|
];
|
|
92464
|
-
if (request.query.sourceUrl) filters.push(
|
|
92465
|
-
if (request.query.targetUrl) filters.push(
|
|
92829
|
+
if (request.query.sourceUrl) filters.push(eq95(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
92830
|
+
if (request.query.targetUrl) filters.push(eq95(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
92466
92831
|
const followable = parseBoolean2(request.query.followable);
|
|
92467
|
-
if (followable != null) filters.push(
|
|
92468
|
-
const where =
|
|
92832
|
+
if (followable != null) filters.push(eq95(siteCrawlEdges.followable, followable));
|
|
92833
|
+
const where = and78(...filters);
|
|
92469
92834
|
const total = app.db.select({ value: count7() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
92470
92835
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
92471
92836
|
const offset = decodeCursor(request.query.cursor);
|
|
92472
|
-
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(
|
|
92837
|
+
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
|
|
92473
92838
|
const nextOffset = offset + rows.length;
|
|
92474
92839
|
return {
|
|
92475
92840
|
project: project.name,
|
|
@@ -92477,7 +92842,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92477
92842
|
runId: snapshot.runId,
|
|
92478
92843
|
...completenessOf(target),
|
|
92479
92844
|
total,
|
|
92480
|
-
nextCursor: nextOffset < total ?
|
|
92845
|
+
nextCursor: nextOffset < total ? encodeCursor3(nextOffset) : null,
|
|
92481
92846
|
templateDetection,
|
|
92482
92847
|
linkKind,
|
|
92483
92848
|
edges: rows.map((row) => mapCrawlEdge(row, templateDetection))
|
|
@@ -92526,17 +92891,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92526
92891
|
};
|
|
92527
92892
|
}
|
|
92528
92893
|
const scope = [
|
|
92529
|
-
|
|
92530
|
-
|
|
92531
|
-
|
|
92532
|
-
|
|
92894
|
+
eq95(siteCrawlEdges.projectId, project.id),
|
|
92895
|
+
eq95(siteCrawlEdges.runId, snapshot.runId),
|
|
92896
|
+
eq95(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
92897
|
+
eq95(siteCrawlEdges.internal, true),
|
|
92533
92898
|
linkKindFilter(linkKind)
|
|
92534
92899
|
];
|
|
92535
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or22(
|
|
92536
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or22(
|
|
92900
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or22(eq95(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq95(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq95(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq95(siteCrawlEdges.targetUrl, request.query.url);
|
|
92901
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or22(eq95(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq95(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq95(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq95(siteCrawlEdges.sourceUrl, request.query.url);
|
|
92537
92902
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
92538
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
92539
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
92903
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and78(...scope, inboundMatch)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
92904
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and78(...scope, outboundMatch)).orderBy(asc18(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
92540
92905
|
return {
|
|
92541
92906
|
project: project.name,
|
|
92542
92907
|
hasCrawlData: true,
|
|
@@ -92570,11 +92935,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92570
92935
|
if (!snapshot.attemptId) {
|
|
92571
92936
|
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
|
|
92572
92937
|
}
|
|
92573
|
-
const where =
|
|
92574
|
-
|
|
92575
|
-
|
|
92576
|
-
|
|
92577
|
-
|
|
92938
|
+
const where = and78(
|
|
92939
|
+
eq95(siteCrawlFindings.projectId, project.id),
|
|
92940
|
+
eq95(siteCrawlFindings.runId, snapshot.runId),
|
|
92941
|
+
eq95(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
92942
|
+
eq95(siteCrawlFindings.findingType, "dead-link"),
|
|
92578
92943
|
// Belt and braces on the client-facing surface: a row without a status
|
|
92579
92944
|
// code is not evidence of a broken link, and this response is where such
|
|
92580
92945
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -92584,12 +92949,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92584
92949
|
// a fabricated broken link shown to a client is not worth the saved
|
|
92585
92950
|
// predicate. Applied to `total` as well as the rows, or the count would
|
|
92586
92951
|
// promise a page the list cannot fill.
|
|
92587
|
-
isNotNull9(
|
|
92952
|
+
isNotNull9(sql35`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
|
|
92588
92953
|
);
|
|
92589
92954
|
const total = app.db.select({ value: count7() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
92590
92955
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
92591
92956
|
const offset = decodeCursor(request.query.cursor);
|
|
92592
|
-
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(
|
|
92957
|
+
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc18(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
|
|
92593
92958
|
const nextOffset = offset + rows.length;
|
|
92594
92959
|
const deadLinks = rows.map((row) => ({
|
|
92595
92960
|
findingKey: row.findingKey,
|
|
@@ -92611,7 +92976,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92611
92976
|
found: snapshot.deadLinksFound,
|
|
92612
92977
|
unverified: snapshot.deadLinksUnverified,
|
|
92613
92978
|
total,
|
|
92614
|
-
nextCursor: nextOffset < total ?
|
|
92979
|
+
nextCursor: nextOffset < total ? encodeCursor3(nextOffset) : null,
|
|
92615
92980
|
deadLinks
|
|
92616
92981
|
};
|
|
92617
92982
|
});
|
|
@@ -92624,16 +92989,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92624
92989
|
createdAt: runs.createdAt,
|
|
92625
92990
|
startedAt: runs.startedAt,
|
|
92626
92991
|
finishedAt: runs.finishedAt
|
|
92627
|
-
}).from(runs).where(
|
|
92628
|
-
|
|
92629
|
-
|
|
92992
|
+
}).from(runs).where(and78(
|
|
92993
|
+
eq95(runs.projectId, project.id),
|
|
92994
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92630
92995
|
notProbeRun()
|
|
92631
92996
|
)).orderBy(desc43(runs.createdAt), desc43(runs.id)).limit(limit).all();
|
|
92632
92997
|
const runIds = rows.map((row) => row.runId);
|
|
92633
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
92634
|
-
|
|
92635
|
-
|
|
92636
|
-
|
|
92998
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq95(siteCrawlSnapshots.runId, runs.id)).where(and78(
|
|
92999
|
+
eq95(siteCrawlSnapshots.projectId, project.id),
|
|
93000
|
+
eq95(runs.projectId, project.id),
|
|
93001
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92637
93002
|
inArray38(runs.status, SURFACEABLE_STATUSES),
|
|
92638
93003
|
notProbeRun(),
|
|
92639
93004
|
inArray38(siteCrawlSnapshots.runId, runIds)
|
|
@@ -92650,21 +93015,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92650
93015
|
});
|
|
92651
93016
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
92652
93017
|
const project = resolveProject(app.db, request.params.name);
|
|
92653
|
-
const run = app.db.select().from(runs).where(
|
|
92654
|
-
|
|
92655
|
-
|
|
92656
|
-
|
|
93018
|
+
const run = app.db.select().from(runs).where(and78(
|
|
93019
|
+
eq95(runs.id, request.params.runId),
|
|
93020
|
+
eq95(runs.projectId, project.id),
|
|
93021
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92657
93022
|
notProbeRun()
|
|
92658
93023
|
)).get();
|
|
92659
93024
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
92660
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
92661
|
-
|
|
92662
|
-
|
|
93025
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and78(
|
|
93026
|
+
eq95(siteCrawlAttempts.projectId, project.id),
|
|
93027
|
+
eq95(siteCrawlAttempts.runId, run.id)
|
|
92663
93028
|
)).orderBy(desc43(siteCrawlAttempts.attemptNumber), desc43(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
92664
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
92665
|
-
|
|
92666
|
-
|
|
92667
|
-
|
|
93029
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and78(
|
|
93030
|
+
eq95(siteCrawlGraphLayouts.projectId, project.id),
|
|
93031
|
+
eq95(siteCrawlGraphLayouts.runId, run.id),
|
|
93032
|
+
eq95(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
92668
93033
|
)).get() : void 0;
|
|
92669
93034
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
92670
93035
|
return {
|
|
@@ -92695,18 +93060,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92695
93060
|
};
|
|
92696
93061
|
});
|
|
92697
93062
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
92698
|
-
const project = tx.select().from(projects).where(
|
|
93063
|
+
const project = tx.select().from(projects).where(eq95(projects.name, request.params.name)).get();
|
|
92699
93064
|
if (!project) throw notFound("Project", request.params.name);
|
|
92700
|
-
const run = tx.select().from(runs).where(
|
|
92701
|
-
|
|
92702
|
-
|
|
92703
|
-
|
|
93065
|
+
const run = tx.select().from(runs).where(and78(
|
|
93066
|
+
eq95(runs.id, request.params.runId),
|
|
93067
|
+
eq95(runs.projectId, project.id),
|
|
93068
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92704
93069
|
notProbeRun()
|
|
92705
93070
|
)).get();
|
|
92706
93071
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
92707
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
92708
|
-
|
|
92709
|
-
|
|
93072
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and78(
|
|
93073
|
+
eq95(siteCrawlAttempts.projectId, project.id),
|
|
93074
|
+
eq95(siteCrawlAttempts.runId, run.id)
|
|
92710
93075
|
)).orderBy(desc43(siteCrawlAttempts.attemptNumber), desc43(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
92711
93076
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
92712
93077
|
const base = {
|
|
@@ -92720,11 +93085,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92720
93085
|
if (!attempt) {
|
|
92721
93086
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
92722
93087
|
}
|
|
92723
|
-
const auditedWhere =
|
|
92724
|
-
|
|
92725
|
-
|
|
92726
|
-
|
|
92727
|
-
|
|
93088
|
+
const auditedWhere = and78(
|
|
93089
|
+
eq95(siteCrawlPages.projectId, project.id),
|
|
93090
|
+
eq95(siteCrawlPages.runId, run.id),
|
|
93091
|
+
eq95(siteCrawlPages.attemptId, attempt.id),
|
|
93092
|
+
eq95(siteCrawlPages.auditState, "success")
|
|
92728
93093
|
);
|
|
92729
93094
|
const pagesAudited = tx.select({ value: count7() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
92730
93095
|
if (state !== "collecting") {
|
|
@@ -92735,11 +93100,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92735
93100
|
url: siteCrawlPages.url,
|
|
92736
93101
|
auditScore: siteCrawlPages.auditScore,
|
|
92737
93102
|
auditFields: siteCrawlPages.auditFields
|
|
92738
|
-
}).from(siteCrawlPages).where(
|
|
93103
|
+
}).from(siteCrawlPages).where(and78(
|
|
92739
93104
|
auditedWhere,
|
|
92740
93105
|
isNotNull9(siteCrawlPages.auditScore),
|
|
92741
93106
|
lt13(siteCrawlPages.auditScore, 70)
|
|
92742
|
-
)).orderBy(
|
|
93107
|
+
)).orderBy(asc18(siteCrawlPages.auditScore), asc18(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
92743
93108
|
const examples = candidates.flatMap((candidate) => {
|
|
92744
93109
|
if (candidate.auditScore == null) return [];
|
|
92745
93110
|
const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
|
|
@@ -92771,12 +93136,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92771
93136
|
status: runs.status,
|
|
92772
93137
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
92773
93138
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
92774
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
92775
|
-
|
|
92776
|
-
|
|
92777
|
-
)).where(
|
|
92778
|
-
|
|
92779
|
-
|
|
93139
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and78(
|
|
93140
|
+
eq95(siteCrawlRunRequests.projectId, runs.projectId),
|
|
93141
|
+
eq95(siteCrawlRunRequests.runId, runs.id)
|
|
93142
|
+
)).where(and78(
|
|
93143
|
+
eq95(runs.projectId, project.id),
|
|
93144
|
+
eq95(runs.kind, RunKinds["site-audit"]),
|
|
92780
93145
|
inArray38(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
92781
93146
|
)).get();
|
|
92782
93147
|
if (existing) {
|
|
@@ -92831,7 +93196,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
92831
93196
|
// ../api-routes/src/research.ts
|
|
92832
93197
|
import crypto54 from "crypto";
|
|
92833
93198
|
import { z as z5 } from "zod";
|
|
92834
|
-
import { and as
|
|
93199
|
+
import { and as and79, count as count8, desc as desc44, eq as eq96, gte as gte25, lt as lt14, or as or23, sql as sql36 } from "drizzle-orm";
|
|
92835
93200
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
92836
93201
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
92837
93202
|
async function researchRoutes(app, opts) {
|
|
@@ -92849,7 +93214,7 @@ async function researchRoutes(app, opts) {
|
|
|
92849
93214
|
const input = parsed.data;
|
|
92850
93215
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
92851
93216
|
const requestHash2 = directResearchRequestHash(input);
|
|
92852
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
93217
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and79(eq96(researchRuns.projectId, project.id), eq96(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
92853
93218
|
if (existingReceipt) {
|
|
92854
93219
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
92855
93220
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -92881,7 +93246,7 @@ async function researchRoutes(app, opts) {
|
|
|
92881
93246
|
const initiatedBy = researchPrincipal(request);
|
|
92882
93247
|
const decision = app.db.transaction((tx) => {
|
|
92883
93248
|
if (input.idempotencyKey) {
|
|
92884
|
-
const existing = tx.select().from(researchRuns).where(
|
|
93249
|
+
const existing = tx.select().from(researchRuns).where(and79(eq96(researchRuns.projectId, project.id), eq96(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
92885
93250
|
if (existing) {
|
|
92886
93251
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
92887
93252
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -92889,11 +93254,11 @@ async function researchRoutes(app, opts) {
|
|
|
92889
93254
|
}
|
|
92890
93255
|
if (initiatedBy?.limited) {
|
|
92891
93256
|
const { start, end, date } = utcDayBounds(now);
|
|
92892
|
-
const used = tx.select({ value: count8() }).from(researchRuns).where(
|
|
92893
|
-
|
|
93257
|
+
const used = tx.select({ value: count8() }).from(researchRuns).where(and79(
|
|
93258
|
+
eq96(researchRuns.projectId, project.id),
|
|
92894
93259
|
gte25(researchRuns.createdAt, start),
|
|
92895
93260
|
lt14(researchRuns.createdAt, end),
|
|
92896
|
-
|
|
93261
|
+
sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
92897
93262
|
)).get()?.value ?? 0;
|
|
92898
93263
|
if (used >= viewerDailyRunLimit) {
|
|
92899
93264
|
throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
@@ -92921,15 +93286,15 @@ async function researchRoutes(app, opts) {
|
|
|
92921
93286
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
92922
93287
|
const requestHash2 = researchBatchRequestHash(input);
|
|
92923
93288
|
const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
|
|
92924
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
92925
|
-
|
|
92926
|
-
|
|
93289
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and79(
|
|
93290
|
+
eq96(researchRuns.projectId, project.id),
|
|
93291
|
+
eq96(researchRuns.idempotencyKey, receiptKeys[0])
|
|
92927
93292
|
)).get();
|
|
92928
93293
|
if (firstReceipt) {
|
|
92929
93294
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
92930
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
92931
|
-
|
|
92932
|
-
|
|
93295
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and79(
|
|
93296
|
+
eq96(researchRuns.projectId, project.id),
|
|
93297
|
+
eq96(researchRuns.idempotencyKey, key)
|
|
92933
93298
|
)).get());
|
|
92934
93299
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
92935
93300
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -92944,11 +93309,11 @@ async function researchRoutes(app, opts) {
|
|
|
92944
93309
|
const decision = app.db.transaction((tx) => {
|
|
92945
93310
|
if (initiatedBy?.limited) {
|
|
92946
93311
|
const { start, end, date } = utcDayBounds(now);
|
|
92947
|
-
const used = tx.select({ value: count8() }).from(researchRuns).where(
|
|
92948
|
-
|
|
93312
|
+
const used = tx.select({ value: count8() }).from(researchRuns).where(and79(
|
|
93313
|
+
eq96(researchRuns.projectId, project.id),
|
|
92949
93314
|
gte25(researchRuns.createdAt, start),
|
|
92950
93315
|
lt14(researchRuns.createdAt, end),
|
|
92951
|
-
|
|
93316
|
+
sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
92952
93317
|
)).get()?.value ?? 0;
|
|
92953
93318
|
if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
92954
93319
|
}
|
|
@@ -92999,9 +93364,9 @@ async function researchRoutes(app, opts) {
|
|
|
92999
93364
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
93000
93365
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
93001
93366
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
93002
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
93003
|
-
|
|
93004
|
-
cursor ? or23(lt14(researchRuns.createdAt, cursor.createdAt),
|
|
93367
|
+
const rows = app.db.select().from(researchRuns).where(and79(
|
|
93368
|
+
eq96(researchRuns.projectId, project.id),
|
|
93369
|
+
cursor ? or23(lt14(researchRuns.createdAt, cursor.createdAt), and79(eq96(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
|
|
93005
93370
|
)).orderBy(desc44(researchRuns.createdAt), desc44(researchRuns.id)).limit(limit + 1).all();
|
|
93006
93371
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
93007
93372
|
const last = runs2.at(-1);
|
|
@@ -93128,9 +93493,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
93128
93493
|
return stored;
|
|
93129
93494
|
}
|
|
93130
93495
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
93131
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
93132
|
-
|
|
93133
|
-
|
|
93496
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and79(
|
|
93497
|
+
eq96(measurementQueryTemplates.projectId, projectId),
|
|
93498
|
+
eq96(measurementQueryTemplates.id, selection.templateId)
|
|
93134
93499
|
)).get();
|
|
93135
93500
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
93136
93501
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -93139,7 +93504,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
93139
93504
|
}
|
|
93140
93505
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
93141
93506
|
if (bindingLocation) {
|
|
93142
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
93507
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq96(projects.id, projectId)).get();
|
|
93143
93508
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
93144
93509
|
}
|
|
93145
93510
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -93152,9 +93517,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
93152
93517
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
93153
93518
|
}
|
|
93154
93519
|
function getDetail(app, projectId, id) {
|
|
93155
|
-
const row = app.db.select().from(researchRuns).where(
|
|
93520
|
+
const row = app.db.select().from(researchRuns).where(and79(eq96(researchRuns.id, id), eq96(researchRuns.projectId, projectId))).get();
|
|
93156
93521
|
if (!row) throw notFound("Research run", id);
|
|
93157
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
93522
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq96(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
93158
93523
|
return { ...serializeRun(row), queries: queries2 };
|
|
93159
93524
|
}
|
|
93160
93525
|
function serializeRun(row) {
|
|
@@ -93184,7 +93549,7 @@ function utcDayBounds(now) {
|
|
|
93184
93549
|
|
|
93185
93550
|
// ../api-routes/src/snapshot-competitor-refresh.ts
|
|
93186
93551
|
import { setImmediate as yieldToEventLoop2 } from "timers/promises";
|
|
93187
|
-
import { and as
|
|
93552
|
+
import { and as and80, asc as asc19, eq as eq97, inArray as inArray39 } from "drizzle-orm";
|
|
93188
93553
|
var ANSWER_MENTIONS_CHUNK_SIZE = 50;
|
|
93189
93554
|
var ANSWER_MENTIONS_MAX_RESTARTS = 3;
|
|
93190
93555
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
@@ -93234,14 +93599,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
93234
93599
|
examined = 0;
|
|
93235
93600
|
wouldUpdate = 0;
|
|
93236
93601
|
mentioned = 0;
|
|
93237
|
-
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(
|
|
93238
|
-
|
|
93239
|
-
|
|
93240
|
-
opts?.runId === void 0 ? void 0 :
|
|
93602
|
+
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and80(
|
|
93603
|
+
eq97(runs.kind, RunKinds["answer-visibility"]),
|
|
93604
|
+
eq97(runs.projectId, projectId),
|
|
93605
|
+
opts?.runId === void 0 ? void 0 : eq97(runs.id, opts.runId)
|
|
93241
93606
|
)).all();
|
|
93242
93607
|
const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
|
|
93243
93608
|
for (const run of runRows) {
|
|
93244
|
-
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
93609
|
+
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq97(querySnapshots.runId, run.id)).orderBy(asc19(querySnapshots.id)).all().map((row) => row.id);
|
|
93245
93610
|
for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
|
|
93246
93611
|
if (chunks++ > 0 && driver.recheckIdentity) {
|
|
93247
93612
|
yield;
|
|
@@ -93317,7 +93682,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
93317
93682
|
} else {
|
|
93318
93683
|
db.transaction((tx) => {
|
|
93319
93684
|
for (const update of pendingUpdates) {
|
|
93320
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
93685
|
+
tx.update(querySnapshots).set(update.patch).where(eq97(querySnapshots.id, update.id)).run();
|
|
93321
93686
|
}
|
|
93322
93687
|
});
|
|
93323
93688
|
updated += pendingUpdates.length;
|
|
@@ -93347,7 +93712,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
93347
93712
|
|
|
93348
93713
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
93349
93714
|
import crypto55 from "crypto";
|
|
93350
|
-
import { and as
|
|
93715
|
+
import { and as and81, eq as eq98 } from "drizzle-orm";
|
|
93351
93716
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
93352
93717
|
return db.transaction((tx) => {
|
|
93353
93718
|
const run = tx.select({
|
|
@@ -93355,7 +93720,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93355
93720
|
trigger: runs.trigger,
|
|
93356
93721
|
status: runs.status,
|
|
93357
93722
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
93358
|
-
}).from(runs).where(
|
|
93723
|
+
}).from(runs).where(and81(eq98(runs.projectId, input.projectId), eq98(runs.id, input.runId))).get();
|
|
93359
93724
|
if (!run) throw notFound("Run", input.runId);
|
|
93360
93725
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
93361
93726
|
return null;
|
|
@@ -93376,7 +93741,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93376
93741
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
93377
93742
|
}
|
|
93378
93743
|
}
|
|
93379
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
93744
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and81(eq98(simpleMeasurementDefinitions.projectId, input.projectId), eq98(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
93380
93745
|
if (existing) {
|
|
93381
93746
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
93382
93747
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -93402,15 +93767,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
93402
93767
|
if (run.status !== RunStatuses.running) {
|
|
93403
93768
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
93404
93769
|
}
|
|
93405
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
93770
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq98(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
93406
93771
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
93407
93772
|
}
|
|
93408
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
93773
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq98(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
93409
93774
|
if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
93410
93775
|
throw validationError("Every captured query must belong to the run project.");
|
|
93411
93776
|
}
|
|
93412
93777
|
if (definition2.competitors !== void 0) {
|
|
93413
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
93778
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq98(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
|
|
93414
93779
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
93415
93780
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
93416
93781
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -93815,8 +94180,8 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93815
94180
|
const resolveToken = opts.resolveCloudRunAccessToken ?? defaultResolveAccessToken;
|
|
93816
94181
|
validators["cloud-run"] = {
|
|
93817
94182
|
validateCredentials: async (source) => {
|
|
93818
|
-
const
|
|
93819
|
-
if (!
|
|
94183
|
+
const record2 = store.getConnection(source.projectName);
|
|
94184
|
+
if (!record2) {
|
|
93820
94185
|
return {
|
|
93821
94186
|
status: CheckStatuses.fail,
|
|
93822
94187
|
code: "traffic.credentials.missing",
|
|
@@ -93825,11 +94190,11 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93825
94190
|
};
|
|
93826
94191
|
}
|
|
93827
94192
|
try {
|
|
93828
|
-
await resolveToken(
|
|
94193
|
+
await resolveToken(record2);
|
|
93829
94194
|
return {
|
|
93830
94195
|
status: CheckStatuses.ok,
|
|
93831
94196
|
code: "traffic.credentials.resolved",
|
|
93832
|
-
summary: `Cloud Run access token resolves for "${source.displayName}" (project ${
|
|
94197
|
+
summary: `Cloud Run access token resolves for "${source.displayName}" (project ${record2.gcpProjectId}).`
|
|
93833
94198
|
};
|
|
93834
94199
|
} catch (e) {
|
|
93835
94200
|
const msg = describeError(e);
|
|
@@ -93855,8 +94220,8 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93855
94220
|
const wordpressTrafficFetch = createGuardedFetch({ allowLoopback: opts.allowLoopbackWebhooks === true });
|
|
93856
94221
|
validators[TrafficSourceTypes.wordpress] = {
|
|
93857
94222
|
validateCredentials: async (source) => {
|
|
93858
|
-
const
|
|
93859
|
-
if (!
|
|
94223
|
+
const record2 = store.getConnection(source.projectName);
|
|
94224
|
+
if (!record2) {
|
|
93860
94225
|
return {
|
|
93861
94226
|
status: CheckStatuses.fail,
|
|
93862
94227
|
code: "traffic.credentials.missing",
|
|
@@ -93866,9 +94231,9 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93866
94231
|
}
|
|
93867
94232
|
try {
|
|
93868
94233
|
await pullEvents({
|
|
93869
|
-
baseUrl:
|
|
93870
|
-
username:
|
|
93871
|
-
applicationPassword:
|
|
94234
|
+
baseUrl: record2.baseUrl,
|
|
94235
|
+
username: record2.username,
|
|
94236
|
+
applicationPassword: record2.applicationPassword,
|
|
93872
94237
|
pageSize: 1,
|
|
93873
94238
|
maxPages: 1,
|
|
93874
94239
|
// The probe carries Basic-auth credentials: check the stored URL
|
|
@@ -93878,7 +94243,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93878
94243
|
return {
|
|
93879
94244
|
status: CheckStatuses.ok,
|
|
93880
94245
|
code: "traffic.credentials.resolved",
|
|
93881
|
-
summary: `WordPress endpoint responds for "${source.displayName}" (${new URL(
|
|
94246
|
+
summary: `WordPress endpoint responds for "${source.displayName}" (${new URL(record2.baseUrl).host}).`
|
|
93882
94247
|
};
|
|
93883
94248
|
} catch (e) {
|
|
93884
94249
|
const httpStatus = e instanceof WordpressTrafficApiError ? e.status : null;
|
|
@@ -93902,8 +94267,8 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93902
94267
|
const pullEvents = opts.pullVercelTrafficEvents ?? listVercelTrafficEvents;
|
|
93903
94268
|
validators[TrafficSourceTypes.vercel] = {
|
|
93904
94269
|
validateCredentials: async (source) => {
|
|
93905
|
-
const
|
|
93906
|
-
if (!
|
|
94270
|
+
const record2 = store.getConnection(source.projectName);
|
|
94271
|
+
if (!record2) {
|
|
93907
94272
|
return {
|
|
93908
94273
|
status: CheckStatuses.fail,
|
|
93909
94274
|
code: "traffic.credentials.missing",
|
|
@@ -93914,10 +94279,10 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93914
94279
|
try {
|
|
93915
94280
|
const probeEnd = Date.now();
|
|
93916
94281
|
await pullEvents({
|
|
93917
|
-
token:
|
|
93918
|
-
projectId:
|
|
93919
|
-
teamId:
|
|
93920
|
-
environment:
|
|
94282
|
+
token: record2.token,
|
|
94283
|
+
projectId: record2.projectId,
|
|
94284
|
+
teamId: record2.teamId,
|
|
94285
|
+
environment: record2.environment,
|
|
93921
94286
|
startDate: probeEnd - 60 * 6e4,
|
|
93922
94287
|
endDate: probeEnd,
|
|
93923
94288
|
maxPages: 1
|
|
@@ -93925,7 +94290,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93925
94290
|
return {
|
|
93926
94291
|
status: CheckStatuses.ok,
|
|
93927
94292
|
code: "traffic.credentials.resolved",
|
|
93928
|
-
summary: `Vercel request-logs responds for "${source.displayName}" (project ${
|
|
94293
|
+
summary: `Vercel request-logs responds for "${source.displayName}" (project ${record2.projectId}).`
|
|
93929
94294
|
};
|
|
93930
94295
|
} catch (e) {
|
|
93931
94296
|
const httpStatus = e instanceof VercelLogsApiError ? e.status : null;
|
|
@@ -93951,8 +94316,8 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93951
94316
|
validateCredentials: (source) => {
|
|
93952
94317
|
const sourceMode = source.configJson.deliveryMode;
|
|
93953
94318
|
const deliveryMode = sourceMode === "queue-pull" ? "queue-pull" : "direct-push";
|
|
93954
|
-
const
|
|
93955
|
-
if (!
|
|
94319
|
+
const record2 = store.getConnectionBySourceId(source.id);
|
|
94320
|
+
if (!record2) {
|
|
93956
94321
|
const projectRecord = store.getConnection(source.projectName);
|
|
93957
94322
|
if (projectRecord && projectRecord.sourceId !== source.id) {
|
|
93958
94323
|
return {
|
|
@@ -93969,7 +94334,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93969
94334
|
remediation: "Re-run `canonry traffic connect cloudflare <project> --zone-id <id>` from the credential-owning host."
|
|
93970
94335
|
};
|
|
93971
94336
|
}
|
|
93972
|
-
const recordMode =
|
|
94337
|
+
const recordMode = record2.deliveryMode;
|
|
93973
94338
|
if (recordMode !== deliveryMode) {
|
|
93974
94339
|
return {
|
|
93975
94340
|
status: CheckStatuses.fail,
|
|
@@ -93978,7 +94343,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93978
94343
|
remediation: "Reconnect the Cloudflare source from the credential-owning host."
|
|
93979
94344
|
};
|
|
93980
94345
|
}
|
|
93981
|
-
if (
|
|
94346
|
+
if (record2.sourceId !== source.id) {
|
|
93982
94347
|
return {
|
|
93983
94348
|
status: CheckStatuses.fail,
|
|
93984
94349
|
code: "traffic.credentials.source-mismatch",
|
|
@@ -93986,9 +94351,9 @@ function buildTrafficSourceValidators(opts) {
|
|
|
93986
94351
|
remediation: "Reconnect the Cloudflare source to pair the credential and source row."
|
|
93987
94352
|
};
|
|
93988
94353
|
}
|
|
93989
|
-
if (
|
|
94354
|
+
if (record2.deliveryMode === "queue-pull") {
|
|
93990
94355
|
const queueConfig = source.configJson;
|
|
93991
|
-
if (typeof
|
|
94356
|
+
if (typeof record2.apiToken !== "string" || record2.apiToken.length === 0 || typeof record2.accountId !== "string" || record2.accountId.length === 0 || typeof record2.queueId !== "string" || record2.queueId.length === 0 || typeof record2.queueName !== "string" || record2.queueName.length === 0 || !Number.isInteger(record2.retentionSeconds) || record2.retentionSeconds < 60 || record2.retentionSeconds > 1209600 || queueConfig.accountId !== record2.accountId || queueConfig.queueId !== record2.queueId || queueConfig.queueName !== record2.queueName || queueConfig.retentionSeconds !== record2.retentionSeconds) {
|
|
93992
94357
|
return {
|
|
93993
94358
|
status: CheckStatuses.fail,
|
|
93994
94359
|
code: "traffic.credentials.queue-mismatch",
|
|
@@ -94002,7 +94367,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
94002
94367
|
summary: `Cloudflare Queue credentials match source "${source.displayName}".`
|
|
94003
94368
|
};
|
|
94004
94369
|
}
|
|
94005
|
-
if (typeof
|
|
94370
|
+
if (typeof record2.bearerToken !== "string" || record2.bearerToken.length === 0 || typeof record2.hmacSecret !== "string" || record2.hmacSecret.length === 0 || !source.ingestTokenHash || hashCloudflareBearerToken(record2.bearerToken) !== source.ingestTokenHash) {
|
|
94006
94371
|
return {
|
|
94007
94372
|
status: CheckStatuses.fail,
|
|
94008
94373
|
code: "traffic.credentials.bearer-mismatch",
|
|
@@ -94321,13 +94686,13 @@ function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
|
|
|
94321
94686
|
var RECURRENCE_LOOKBACK_RUNS = 5;
|
|
94322
94687
|
var HISTORY_WINDOW_RUNS = Math.max(PERSISTENT_GAP_THRESHOLD, 5);
|
|
94323
94688
|
var log = createLogger("IntelligenceService");
|
|
94324
|
-
var runChronologyKey =
|
|
94689
|
+
var runChronologyKey = sql37`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
94325
94690
|
function analyzableRunPredicates(projectId) {
|
|
94326
94691
|
return [
|
|
94327
|
-
|
|
94692
|
+
eq99(runs.projectId, projectId),
|
|
94328
94693
|
// Only this kind writes query_snapshots.
|
|
94329
|
-
|
|
94330
|
-
or24(
|
|
94694
|
+
eq99(runs.kind, RunKinds["answer-visibility"]),
|
|
94695
|
+
or24(eq99(runs.status, RunStatuses.completed), eq99(runs.status, RunStatuses.partial)),
|
|
94331
94696
|
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
94332
94697
|
// is called, but a future call site invoking it directly for a probe must
|
|
94333
94698
|
// still not pollute the intelligence window.
|
|
@@ -94372,16 +94737,16 @@ var IntelligenceService = class {
|
|
|
94372
94737
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
94373
94738
|
*/
|
|
94374
94739
|
analyzeAndPersist(runId, projectId) {
|
|
94375
|
-
const currentRunRecord = this.db.select().from(runs).where(
|
|
94740
|
+
const currentRunRecord = this.db.select().from(runs).where(and82(eq99(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
94376
94741
|
if (!currentRunRecord) {
|
|
94377
94742
|
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
94378
94743
|
return null;
|
|
94379
94744
|
}
|
|
94380
94745
|
const currentLocation = currentRunRecord.location ?? null;
|
|
94381
94746
|
const recentRuns = this.db.select().from(runs).where(
|
|
94382
|
-
|
|
94747
|
+
and82(
|
|
94383
94748
|
...analyzableRunPredicates(projectId),
|
|
94384
|
-
currentLocation === null ? isNull19(runs.location) :
|
|
94749
|
+
currentLocation === null ? isNull19(runs.location) : eq99(runs.location, currentLocation),
|
|
94385
94750
|
// Never look forward. The window is this run's own past, so
|
|
94386
94751
|
// re-analyzing a historical run compares it against its true
|
|
94387
94752
|
// predecessor rather than against sweeps that came after it.
|
|
@@ -94389,7 +94754,7 @@ var IntelligenceService = class {
|
|
|
94389
94754
|
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
94390
94755
|
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
94391
94756
|
exists2(
|
|
94392
|
-
this.db.select({ one:
|
|
94757
|
+
this.db.select({ one: sql37`1` }).from(querySnapshots).where(eq99(querySnapshots.runId, runs.id))
|
|
94393
94758
|
)
|
|
94394
94759
|
)
|
|
94395
94760
|
).orderBy(desc45(runs.finishedAt), desc45(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
@@ -94465,7 +94830,7 @@ var IntelligenceService = class {
|
|
|
94465
94830
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
94466
94831
|
*/
|
|
94467
94832
|
analyzeAndPersistGbp(runId, projectId) {
|
|
94468
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
94833
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq99(runs.id, runId)).get();
|
|
94469
94834
|
if (!runRow) {
|
|
94470
94835
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
94471
94836
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -94473,9 +94838,9 @@ var IntelligenceService = class {
|
|
|
94473
94838
|
}
|
|
94474
94839
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
94475
94840
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
94476
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
94477
|
-
|
|
94478
|
-
|
|
94841
|
+
const selected = this.db.select().from(gbpLocations).where(and82(
|
|
94842
|
+
eq99(gbpLocations.projectId, projectId),
|
|
94843
|
+
eq99(gbpLocations.selected, true),
|
|
94479
94844
|
gte26(gbpLocations.syncedAt, windowStart),
|
|
94480
94845
|
lte23(gbpLocations.syncedAt, windowEnd)
|
|
94481
94846
|
)).all();
|
|
@@ -94510,12 +94875,12 @@ var IntelligenceService = class {
|
|
|
94510
94875
|
}
|
|
94511
94876
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
94512
94877
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
94513
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
94514
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
94515
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
94516
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
94878
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and82(eq99(gbpDailyMetrics.projectId, projectId), eq99(gbpDailyMetrics.locationName, locationName))).all();
|
|
94879
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and82(eq99(gbpPlaceActions.projectId, projectId), eq99(gbpPlaceActions.locationName, locationName))).all();
|
|
94880
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and82(eq99(gbpLodgingSnapshots.projectId, projectId), eq99(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc45(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
94881
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and82(eq99(gbpLocations.projectId, projectId), eq99(gbpLocations.locationName, locationName))).get();
|
|
94517
94882
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
94518
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
94883
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and82(eq99(gbpPlaceDetails.projectId, projectId), eq99(gbpPlaceDetails.locationName, locationName))).orderBy(desc45(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
94519
94884
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
94520
94885
|
const summary3 = buildGbpSummary({
|
|
94521
94886
|
locationName,
|
|
@@ -94551,7 +94916,7 @@ var IntelligenceService = class {
|
|
|
94551
94916
|
/** Build the month-over-month keyword series for a location from the
|
|
94552
94917
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
94553
94918
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
94554
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
94919
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and82(eq99(gbpKeywordMonthly.projectId, projectId), eq99(gbpKeywordMonthly.locationName, locationName))).all();
|
|
94555
94920
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
94556
94921
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
94557
94922
|
const recentMonth = months[0] ?? null;
|
|
@@ -94582,7 +94947,7 @@ var IntelligenceService = class {
|
|
|
94582
94947
|
*/
|
|
94583
94948
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
94584
94949
|
const covered = new Set(coveredLocationNames);
|
|
94585
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
94950
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and82(eq99(insights.projectId, projectId), eq99(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
94586
94951
|
const staleIds = [];
|
|
94587
94952
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
94588
94953
|
for (const row of existing) {
|
|
@@ -94593,7 +94958,7 @@ var IntelligenceService = class {
|
|
|
94593
94958
|
}
|
|
94594
94959
|
this.db.transaction((tx) => {
|
|
94595
94960
|
for (const id of staleIds) {
|
|
94596
|
-
tx.delete(insights).where(
|
|
94961
|
+
tx.delete(insights).where(eq99(insights.id, id)).run();
|
|
94597
94962
|
}
|
|
94598
94963
|
for (const insight of gbpInsights) {
|
|
94599
94964
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -94680,7 +95045,7 @@ var IntelligenceService = class {
|
|
|
94680
95045
|
* create per run + aggregate). DB is left untouched.
|
|
94681
95046
|
*/
|
|
94682
95047
|
backfill(projectName, opts, onProgress) {
|
|
94683
|
-
const project = this.db.select().from(projects).where(
|
|
95048
|
+
const project = this.db.select().from(projects).where(eq99(projects.name, projectName)).get();
|
|
94684
95049
|
if (!project) {
|
|
94685
95050
|
throw new Error(`Project "${projectName}" not found`);
|
|
94686
95051
|
}
|
|
@@ -94692,7 +95057,7 @@ var IntelligenceService = class {
|
|
|
94692
95057
|
}
|
|
94693
95058
|
sinceTimestamp = parsed;
|
|
94694
95059
|
}
|
|
94695
|
-
const allRuns = this.db.select().from(runs).where(
|
|
95060
|
+
const allRuns = this.db.select().from(runs).where(and82(...analyzableRunPredicates(project.id))).orderBy(asc20(runs.finishedAt)).all();
|
|
94696
95061
|
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
94697
95062
|
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
94698
95063
|
let startIdx = 0;
|
|
@@ -94774,7 +95139,7 @@ var IntelligenceService = class {
|
|
|
94774
95139
|
* signals match the same identities the dashboard and reports do.
|
|
94775
95140
|
*/
|
|
94776
95141
|
loadTrackedCompetitors(projectId) {
|
|
94777
|
-
return this.db.select(competitorIdentityColumns).from(competitors).where(
|
|
95142
|
+
return this.db.select(competitorIdentityColumns).from(competitors).where(eq99(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
94778
95143
|
}
|
|
94779
95144
|
/**
|
|
94780
95145
|
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
@@ -94805,15 +95170,15 @@ var IntelligenceService = class {
|
|
|
94805
95170
|
}
|
|
94806
95171
|
persistResult(result, runId, projectId) {
|
|
94807
95172
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
94808
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
95173
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq99(insights.runId, runId)).all();
|
|
94809
95174
|
for (const row of existingInsights) {
|
|
94810
95175
|
if (row.dismissed) {
|
|
94811
95176
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
94812
95177
|
}
|
|
94813
95178
|
}
|
|
94814
95179
|
this.db.transaction((tx) => {
|
|
94815
|
-
tx.delete(insights).where(
|
|
94816
|
-
tx.delete(healthSnapshots).where(
|
|
95180
|
+
tx.delete(insights).where(eq99(insights.runId, runId)).run();
|
|
95181
|
+
tx.delete(healthSnapshots).where(eq99(healthSnapshots.runId, runId)).run();
|
|
94817
95182
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
94818
95183
|
for (const insight of result.insights) {
|
|
94819
95184
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -94860,8 +95225,8 @@ var IntelligenceService = class {
|
|
|
94860
95225
|
clicks: gscSearchData.clicks,
|
|
94861
95226
|
impressions: gscSearchData.impressions,
|
|
94862
95227
|
position: gscSearchData.position
|
|
94863
|
-
}).from(gscSearchData).where(
|
|
94864
|
-
|
|
95228
|
+
}).from(gscSearchData).where(and82(
|
|
95229
|
+
eq99(gscSearchData.projectId, projectId),
|
|
94865
95230
|
gte26(gscSearchData.date, window.startDate),
|
|
94866
95231
|
lte23(gscSearchData.date, window.endDate)
|
|
94867
95232
|
)).all();
|
|
@@ -94914,17 +95279,17 @@ var IntelligenceService = class {
|
|
|
94914
95279
|
const key = row.query.toLowerCase();
|
|
94915
95280
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
94916
95281
|
}
|
|
94917
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
95282
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq99(projects.id, projectId)).get();
|
|
94918
95283
|
const locationCount = Math.max(
|
|
94919
95284
|
1,
|
|
94920
95285
|
(projectRow?.locations ?? []).length
|
|
94921
95286
|
);
|
|
94922
95287
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
94923
95288
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
94924
|
-
|
|
94925
|
-
|
|
94926
|
-
|
|
94927
|
-
or24(
|
|
95289
|
+
and82(
|
|
95290
|
+
eq99(runs.projectId, projectId),
|
|
95291
|
+
eq99(runs.kind, RunKinds["answer-visibility"]),
|
|
95292
|
+
or24(eq99(runs.status, "completed"), eq99(runs.status, "partial")),
|
|
94928
95293
|
// Defensive — see top of file.
|
|
94929
95294
|
ne17(runs.trigger, RunTriggers.probe)
|
|
94930
95295
|
)
|
|
@@ -94944,7 +95309,7 @@ var IntelligenceService = class {
|
|
|
94944
95309
|
const haveHistory = recentRunIds.length > 0;
|
|
94945
95310
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
94946
95311
|
if (haveHistory) {
|
|
94947
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
95312
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and82(eq99(insights.type, "regression"), inArray40(insights.runId, recentRunIds))).all();
|
|
94948
95313
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
94949
95314
|
for (const row of priorRows) {
|
|
94950
95315
|
if (!row.runId) continue;
|
|
@@ -94973,7 +95338,7 @@ var IntelligenceService = class {
|
|
|
94973
95338
|
});
|
|
94974
95339
|
}
|
|
94975
95340
|
buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
|
|
94976
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
95341
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq99(projects.id, projectId)).get();
|
|
94977
95342
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
94978
95343
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
94979
95344
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -94991,7 +95356,7 @@ var IntelligenceService = class {
|
|
|
94991
95356
|
citedDomains: querySnapshots.citedDomains,
|
|
94992
95357
|
rawResponse: querySnapshots.rawResponse,
|
|
94993
95358
|
snapshotLocation: querySnapshots.location
|
|
94994
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
95359
|
+
}).from(querySnapshots).leftJoin(queries, eq99(querySnapshots.queryId, queries.id)).where(eq99(querySnapshots.runId, runId)).all();
|
|
94995
95360
|
const snapshots = [];
|
|
94996
95361
|
let orphanCount = 0;
|
|
94997
95362
|
for (const r of rows) {
|