@houtini/seo-audit-console 0.2.0
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/LICENSE +92 -0
- package/README.md +211 -0
- package/dist/audit/checks.d.ts +35 -0
- package/dist/audit/checks.d.ts.map +1 -0
- package/dist/audit/checks.js +1475 -0
- package/dist/audit/checks.js.map +1 -0
- package/dist/audit/drift.d.ts +40 -0
- package/dist/audit/drift.d.ts.map +1 -0
- package/dist/audit/drift.js +148 -0
- package/dist/audit/drift.js.map +1 -0
- package/dist/audit/engine.d.ts +33 -0
- package/dist/audit/engine.d.ts.map +1 -0
- package/dist/audit/engine.js +186 -0
- package/dist/audit/engine.js.map +1 -0
- package/dist/audit/opportunities.d.ts +23 -0
- package/dist/audit/opportunities.d.ts.map +1 -0
- package/dist/audit/opportunities.js +149 -0
- package/dist/audit/opportunities.js.map +1 -0
- package/dist/audit/report.d.ts +11 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/report.js +59 -0
- package/dist/audit/report.js.map +1 -0
- package/dist/audit/schema-validate.d.ts +35 -0
- package/dist/audit/schema-validate.d.ts.map +1 -0
- package/dist/audit/schema-validate.js +293 -0
- package/dist/audit/schema-validate.js.map +1 -0
- package/dist/audit/templates.d.ts +43 -0
- package/dist/audit/templates.d.ts.map +1 -0
- package/dist/audit/templates.js +129 -0
- package/dist/audit/templates.js.map +1 -0
- package/dist/audit/topicGaps.d.ts +42 -0
- package/dist/audit/topicGaps.d.ts.map +1 -0
- package/dist/audit/topicGaps.js +181 -0
- package/dist/audit/topicGaps.js.map +1 -0
- package/dist/core/AuditDatabase.d.ts +33 -0
- package/dist/core/AuditDatabase.d.ts.map +1 -0
- package/dist/core/AuditDatabase.js +481 -0
- package/dist/core/AuditDatabase.js.map +1 -0
- package/dist/core/Backlinks.d.ts +33 -0
- package/dist/core/Backlinks.d.ts.map +1 -0
- package/dist/core/Backlinks.js +110 -0
- package/dist/core/Backlinks.js.map +1 -0
- package/dist/core/Crawler.d.ts +23 -0
- package/dist/core/Crawler.d.ts.map +1 -0
- package/dist/core/Crawler.js +588 -0
- package/dist/core/Crawler.js.map +1 -0
- package/dist/core/DataForSeoClient.d.ts +86 -0
- package/dist/core/DataForSeoClient.d.ts.map +1 -0
- package/dist/core/DataForSeoClient.js +232 -0
- package/dist/core/DataForSeoClient.js.map +1 -0
- package/dist/core/Entities.d.ts +23 -0
- package/dist/core/Entities.d.ts.map +1 -0
- package/dist/core/Entities.js +62 -0
- package/dist/core/Entities.js.map +1 -0
- package/dist/core/GscClient.d.ts +22 -0
- package/dist/core/GscClient.d.ts.map +1 -0
- package/dist/core/GscClient.js +93 -0
- package/dist/core/GscClient.js.map +1 -0
- package/dist/core/GscSync.d.ts +20 -0
- package/dist/core/GscSync.d.ts.map +1 -0
- package/dist/core/GscSync.js +133 -0
- package/dist/core/GscSync.js.map +1 -0
- package/dist/core/JobManager.d.ts +30 -0
- package/dist/core/JobManager.d.ts.map +1 -0
- package/dist/core/JobManager.js +68 -0
- package/dist/core/JobManager.js.map +1 -0
- package/dist/core/RankTracker.d.ts +25 -0
- package/dist/core/RankTracker.d.ts.map +1 -0
- package/dist/core/RankTracker.js +78 -0
- package/dist/core/RankTracker.js.map +1 -0
- package/dist/core/Refresh.d.ts +32 -0
- package/dist/core/Refresh.d.ts.map +1 -0
- package/dist/core/Refresh.js +73 -0
- package/dist/core/Refresh.js.map +1 -0
- package/dist/core/UrlInspector.d.ts +22 -0
- package/dist/core/UrlInspector.d.ts.map +1 -0
- package/dist/core/UrlInspector.js +92 -0
- package/dist/core/UrlInspector.js.map +1 -0
- package/dist/core/WikidataClient.d.ts +19 -0
- package/dist/core/WikidataClient.d.ts.map +1 -0
- package/dist/core/WikidataClient.js +59 -0
- package/dist/core/WikidataClient.js.map +1 -0
- package/dist/core/agentReadiness.d.ts +34 -0
- package/dist/core/agentReadiness.d.ts.map +1 -0
- package/dist/core/agentReadiness.js +119 -0
- package/dist/core/agentReadiness.js.map +1 -0
- package/dist/core/ctrModel.d.ts +2 -0
- package/dist/core/ctrModel.d.ts.map +1 -0
- package/dist/core/ctrModel.js +6 -0
- package/dist/core/ctrModel.js.map +1 -0
- package/dist/core/dashboardData.d.ts +312 -0
- package/dist/core/dashboardData.d.ts.map +1 -0
- package/dist/core/dashboardData.js +550 -0
- package/dist/core/dashboardData.js.map +1 -0
- package/dist/core/dataStorage.d.ts +42 -0
- package/dist/core/dataStorage.d.ts.map +1 -0
- package/dist/core/dataStorage.js +193 -0
- package/dist/core/dataStorage.js.map +1 -0
- package/dist/core/draftBrief.d.ts +27 -0
- package/dist/core/draftBrief.d.ts.map +1 -0
- package/dist/core/draftBrief.js +69 -0
- package/dist/core/draftBrief.js.map +1 -0
- package/dist/core/extract.d.ts +67 -0
- package/dist/core/extract.d.ts.map +1 -0
- package/dist/core/extract.js +262 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/gscFreshness.d.ts +18 -0
- package/dist/core/gscFreshness.d.ts.map +1 -0
- package/dist/core/gscFreshness.js +32 -0
- package/dist/core/gscFreshness.js.map +1 -0
- package/dist/core/linkGraph.d.ts +19 -0
- package/dist/core/linkGraph.d.ts.map +1 -0
- package/dist/core/linkGraph.js +125 -0
- package/dist/core/linkGraph.js.map +1 -0
- package/dist/core/passageScore.d.ts +19 -0
- package/dist/core/passageScore.d.ts.map +1 -0
- package/dist/core/passageScore.js +59 -0
- package/dist/core/passageScore.js.map +1 -0
- package/dist/core/paths.d.ts +5 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +15 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/queryData.d.ts +35 -0
- package/dist/core/queryData.d.ts.map +1 -0
- package/dist/core/queryData.js +200 -0
- package/dist/core/queryData.js.map +1 -0
- package/dist/core/reranker.d.ts +8 -0
- package/dist/core/reranker.d.ts.map +1 -0
- package/dist/core/reranker.js +68 -0
- package/dist/core/reranker.js.map +1 -0
- package/dist/core/robots.d.ts +11 -0
- package/dist/core/robots.d.ts.map +1 -0
- package/dist/core/robots.js +76 -0
- package/dist/core/robots.js.map +1 -0
- package/dist/core/sitemap.d.ts +15 -0
- package/dist/core/sitemap.d.ts.map +1 -0
- package/dist/core/sitemap.js +100 -0
- package/dist/core/sitemap.js.map +1 -0
- package/dist/core/sql.d.ts +6 -0
- package/dist/core/sql.d.ts.map +1 -0
- package/dist/core/sql.js +6 -0
- package/dist/core/sql.js.map +1 -0
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/url-key.d.ts +39 -0
- package/dist/core/url-key.d.ts.map +1 -0
- package/dist/core/url-key.js +106 -0
- package/dist/core/url-key.js.map +1 -0
- package/dist/generators/index.d.ts +45 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +184 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1387 -0
- package/dist/server.js.map +1 -0
- package/dist/src/ui/dashboard.html +347 -0
- package/dist/src/ui/sync-progress.html +104 -0
- package/package.json +101 -0
- package/server.json +57 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const QUERYABLE_TABLES: readonly ["pages", "links", "search_analytics", "url_inspection", "sitemap_urls", "findings", "image_assets", "page_backlinks"];
|
|
2
|
+
export type QueryTable = (typeof QUERYABLE_TABLES)[number];
|
|
3
|
+
export declare const FILTER_OPS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "like", "in"];
|
|
4
|
+
export type FilterOp = (typeof FILTER_OPS)[number];
|
|
5
|
+
export declare const METRIC_FNS: readonly ["sum", "avg", "min", "max"];
|
|
6
|
+
export type MetricFn = (typeof METRIC_FNS)[number];
|
|
7
|
+
export interface QueryFilter {
|
|
8
|
+
column: string;
|
|
9
|
+
op: FilterOp;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface QueryMetric {
|
|
13
|
+
fn: MetricFn;
|
|
14
|
+
column: string;
|
|
15
|
+
}
|
|
16
|
+
export interface QueryDataInput {
|
|
17
|
+
table: QueryTable;
|
|
18
|
+
mode?: 'aggregate' | 'rows';
|
|
19
|
+
groupBy?: string[];
|
|
20
|
+
metrics?: QueryMetric[];
|
|
21
|
+
filters?: QueryFilter[];
|
|
22
|
+
orderBy?: string;
|
|
23
|
+
orderDir?: 'asc' | 'desc';
|
|
24
|
+
limit?: number;
|
|
25
|
+
offset?: number;
|
|
26
|
+
columns?: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface QueryDataResult {
|
|
29
|
+
markdown: string;
|
|
30
|
+
structured: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** Truncate a cell value at CELL_CAP chars — always loud (trailing …). */
|
|
33
|
+
export declare function truncateCell(v: unknown): unknown;
|
|
34
|
+
export declare function runQueryData(dbPath: string, input: QueryDataInput): QueryDataResult;
|
|
35
|
+
//# sourceMappingURL=queryData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryData.d.ts","sourceRoot":"","sources":["../../src/core/queryData.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,gBAAgB,iIAGnB,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,eAAO,MAAM,UAAU,+DAAgE,CAAC;AACxF,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAKnD,eAAO,MAAM,UAAU,uCAAwC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,MAAM,WAAW,WAAW;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE;AAC7E,MAAM,WAAW,WAAW;IAAG,EAAE,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAE7D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAiBD,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAGhD;AAWD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,eAAe,CAenF"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* queryData — the composition-layer multiplier: aggregate IN the database, return
|
|
3
|
+
* answers, not rows. Read-only (SQLite opened readonly, SELECTs only). Every
|
|
4
|
+
* identifier (table, column) is validated against the table's actual columns via
|
|
5
|
+
* PRAGMA table_info; every VALUE travels as a bound parameter — nothing user-supplied
|
|
6
|
+
* is ever interpolated into SQL.
|
|
7
|
+
*/
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
export const QUERYABLE_TABLES = [
|
|
10
|
+
'pages', 'links', 'search_analytics', 'url_inspection', 'sitemap_urls',
|
|
11
|
+
'findings', 'image_assets', 'page_backlinks',
|
|
12
|
+
];
|
|
13
|
+
export const FILTER_OPS = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'like', 'in'];
|
|
14
|
+
const OP_SQL = {
|
|
15
|
+
eq: '=', ne: '<>', gt: '>', gte: '>=', lt: '<', lte: '<=', like: 'LIKE',
|
|
16
|
+
};
|
|
17
|
+
export const METRIC_FNS = ['sum', 'avg', 'min', 'max'];
|
|
18
|
+
/** Sensible default columns per table for rows mode — the fat columns (body_chunks,
|
|
19
|
+
* json_ld, evidence…) stay out unless explicitly requested. */
|
|
20
|
+
const DEFAULT_ROW_COLUMNS = {
|
|
21
|
+
pages: ['url', 'status_code', 'indexable', 'title', 'h1', 'word_count', 'ipr', 'click_depth', 'inlink_count', 'canonical_key'],
|
|
22
|
+
links: ['source_key', 'target_key', 'anchor_text', 'is_internal', 'placement', 'rel'],
|
|
23
|
+
search_analytics: ['date', 'query', 'page_key', 'clicks', 'impressions', 'ctr', 'position'],
|
|
24
|
+
url_inspection: ['url_key', 'verdict', 'coverage_state', 'google_canonical', 'user_canonical', 'last_crawl_time'],
|
|
25
|
+
sitemap_urls: ['url', 'lastmod'],
|
|
26
|
+
findings: ['id', 'run_id', 'check_id', 'category', 'severity', 'url_key', 'priority'],
|
|
27
|
+
image_assets: ['url', 'bytes', 'status', 'content_type', 'used_on'],
|
|
28
|
+
page_backlinks: ['url', 'backlinks', 'referring_domains', 'dofollow', 'status_code'],
|
|
29
|
+
};
|
|
30
|
+
const CELL_CAP = 120;
|
|
31
|
+
/** Truncate a cell value at CELL_CAP chars — always loud (trailing …). */
|
|
32
|
+
export function truncateCell(v) {
|
|
33
|
+
if (typeof v !== 'string' || v.length <= CELL_CAP)
|
|
34
|
+
return v;
|
|
35
|
+
return v.slice(0, CELL_CAP) + '…';
|
|
36
|
+
}
|
|
37
|
+
const mdCell = (v) => {
|
|
38
|
+
if (v == null)
|
|
39
|
+
return '—';
|
|
40
|
+
return String(truncateCell(v)).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
41
|
+
};
|
|
42
|
+
const quoteIdent = (name) => `"${name.replace(/"/g, '""')}"`;
|
|
43
|
+
const NUMERIC_AFFINITY = /INT|REAL|FLOA|DOUB|NUM|DECI/i;
|
|
44
|
+
export function runQueryData(dbPath, input) {
|
|
45
|
+
let db;
|
|
46
|
+
try {
|
|
47
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {
|
|
51
|
+
markdown: `No database at ${dbPath} — run refresh_property (or start_crawl / sync_gsc) first.`,
|
|
52
|
+
structured: { error: 'no_database', dbPath },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return execute(db, input);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
db.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function execute(db, input) {
|
|
63
|
+
const table = input.table;
|
|
64
|
+
if (!QUERYABLE_TABLES.includes(table))
|
|
65
|
+
throw new Error(`Unknown table: ${table}`);
|
|
66
|
+
// The column whitelist IS the table's real columns (PRAGMA table_info) — anything
|
|
67
|
+
// not in it is hard-rejected. Numeric columns (INTEGER/REAL affinity) additionally
|
|
68
|
+
// qualify for sum/avg/min/max.
|
|
69
|
+
const info = db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all();
|
|
70
|
+
if (!info.length) {
|
|
71
|
+
return { markdown: `Table ${table} has no columns in this database (nothing synced yet?).`, structured: { error: 'empty_table_schema', table } };
|
|
72
|
+
}
|
|
73
|
+
const allCols = new Set(info.map(c => c.name));
|
|
74
|
+
const numericCols = new Set(info.filter(c => NUMERIC_AFFINITY.test(c.type ?? '')).map(c => c.name));
|
|
75
|
+
const requireCol = (name, context) => {
|
|
76
|
+
if (!allCols.has(name))
|
|
77
|
+
throw new Error(`Unknown column in ${context}: "${name}". Columns on ${table}: ${[...allCols].join(', ')}`);
|
|
78
|
+
return name;
|
|
79
|
+
};
|
|
80
|
+
const requireNumeric = (name, context) => {
|
|
81
|
+
requireCol(name, context);
|
|
82
|
+
if (!numericCols.has(name))
|
|
83
|
+
throw new Error(`Column "${name}" is not numeric — ${context} needs one of: ${[...numericCols].join(', ')}`);
|
|
84
|
+
return name;
|
|
85
|
+
};
|
|
86
|
+
// WHERE — column names from the whitelist, values ALWAYS as bound parameters.
|
|
87
|
+
const whereParts = [];
|
|
88
|
+
const params = [];
|
|
89
|
+
for (const f of input.filters ?? []) {
|
|
90
|
+
const col = quoteIdent(requireCol(f.column, 'filters'));
|
|
91
|
+
if (f.op === 'in') {
|
|
92
|
+
if (!Array.isArray(f.value) || !f.value.length)
|
|
93
|
+
throw new Error(`Filter op "in" on ${f.column} needs a non-empty array value.`);
|
|
94
|
+
if (f.value.length > 100)
|
|
95
|
+
throw new Error(`Filter op "in" on ${f.column}: max 100 values.`);
|
|
96
|
+
whereParts.push(`${col} IN (${f.value.map(() => '?').join(',')})`);
|
|
97
|
+
params.push(...f.value);
|
|
98
|
+
}
|
|
99
|
+
else if (f.value === null && (f.op === 'eq' || f.op === 'ne')) {
|
|
100
|
+
whereParts.push(`${col} IS ${f.op === 'eq' ? '' : 'NOT '}NULL`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
if (f.value === null || Array.isArray(f.value) || typeof f.value === 'object') {
|
|
104
|
+
throw new Error(`Filter op "${f.op}" on ${f.column} needs a scalar value.`);
|
|
105
|
+
}
|
|
106
|
+
whereParts.push(`${col} ${OP_SQL[f.op]} ?`);
|
|
107
|
+
params.push(f.value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const where = whereParts.length ? ` WHERE ${whereParts.join(' AND ')}` : '';
|
|
111
|
+
const from = `FROM ${quoteIdent(table)}${where}`;
|
|
112
|
+
const mode = input.mode ?? 'aggregate';
|
|
113
|
+
const orderDir = input.orderDir === 'asc' ? 'ASC' : 'DESC';
|
|
114
|
+
// Total matching rows — both modes report it (the honest denominator).
|
|
115
|
+
const rowsTotal = db.prepare(`SELECT COUNT(*) n ${from}`).get(...params).n;
|
|
116
|
+
if (mode === 'aggregate') {
|
|
117
|
+
const groupBy = (input.groupBy ?? []).map(c => requireCol(c, 'groupBy'));
|
|
118
|
+
const metrics = (input.metrics ?? []).map(m => {
|
|
119
|
+
if (!METRIC_FNS.includes(m.fn))
|
|
120
|
+
throw new Error(`Unknown metric fn: ${m.fn}`);
|
|
121
|
+
return { fn: m.fn, column: requireNumeric(m.column, `metrics (${m.fn})`), alias: `${m.fn}_${m.column}` };
|
|
122
|
+
});
|
|
123
|
+
const sel = [
|
|
124
|
+
...groupBy.map(quoteIdent),
|
|
125
|
+
'COUNT(*) AS count',
|
|
126
|
+
...metrics.map(m => `${m.fn.toUpperCase()}(${quoteIdent(m.column)}) AS ${quoteIdent(m.alias)}`),
|
|
127
|
+
].join(', ');
|
|
128
|
+
const groupClause = groupBy.length ? ` GROUP BY ${groupBy.map(quoteIdent).join(', ')}` : '';
|
|
129
|
+
// orderBy: a groupBy column, 'count', or a requested metric alias (e.g. sum_clicks).
|
|
130
|
+
const orderable = new Set(['count', ...groupBy, ...metrics.map(m => m.alias)]);
|
|
131
|
+
const orderCol = input.orderBy ?? 'count';
|
|
132
|
+
if (!orderable.has(orderCol))
|
|
133
|
+
throw new Error(`orderBy "${orderCol}" must be one of: ${[...orderable].join(', ')}`);
|
|
134
|
+
const limit = Math.min(Math.max(input.limit ?? 25, 1), 500);
|
|
135
|
+
const sql = `SELECT ${sel} ${from}${groupClause} ORDER BY ${quoteIdent(orderCol)} ${orderDir} LIMIT ${limit}`;
|
|
136
|
+
const groups = db.prepare(sql).all(...params);
|
|
137
|
+
const groupsTotal = groupBy.length
|
|
138
|
+
? db.prepare(`SELECT COUNT(*) n FROM (SELECT 1 ${from}${groupClause})`).get(...params).n
|
|
139
|
+
: 1;
|
|
140
|
+
// Round avg to 2dp; attach SF-style percentages.
|
|
141
|
+
const out = groups.map(g => {
|
|
142
|
+
const row = {};
|
|
143
|
+
for (const c of groupBy)
|
|
144
|
+
row[c] = truncateCell(g[c]);
|
|
145
|
+
const count = Number(g.count) || 0;
|
|
146
|
+
row.count = count;
|
|
147
|
+
row.pct = rowsTotal > 0 ? Math.round((count / rowsTotal) * 1000) / 10 : 0;
|
|
148
|
+
for (const m of metrics) {
|
|
149
|
+
const v = g[m.alias];
|
|
150
|
+
row[m.alias] = m.fn === 'avg' && typeof v === 'number' ? Math.round(v * 100) / 100 : v;
|
|
151
|
+
}
|
|
152
|
+
return row;
|
|
153
|
+
});
|
|
154
|
+
const headerCols = [...groupBy, 'count', '%', ...metrics.map(m => m.alias)];
|
|
155
|
+
const header = `| ${headerCols.join(' | ')} |\n|${headerCols.map(() => '---').join('|')}|`;
|
|
156
|
+
const body = out.map(r => `| ${[...groupBy.map(c => mdCell(r[c])), String(r.count), `${r.pct}%`, ...metrics.map(m => mdCell(r[m.alias]))].join(' | ')} |`).join('\n');
|
|
157
|
+
const shownPct = rowsTotal > 0
|
|
158
|
+
? Math.round((out.reduce((s, r) => s + r.count, 0) / rowsTotal) * 1000) / 10
|
|
159
|
+
: 0;
|
|
160
|
+
const totalLine = groupBy.length
|
|
161
|
+
? `Total: ${rowsTotal.toLocaleString('en-US')} rows across ${groupsTotal.toLocaleString('en-US')} groups — showing ${out.length}${groupsTotal > out.length ? ` (covering ${shownPct}% of rows; raise limit or add filters for the rest)` : ''}.`
|
|
162
|
+
: `Total: ${rowsTotal.toLocaleString('en-US')} rows (no groupBy — single aggregate).`;
|
|
163
|
+
const markdown = out.length ? `${header}\n${body}\n\n${totalLine}` : `No rows match. ${totalLine}`;
|
|
164
|
+
return {
|
|
165
|
+
markdown,
|
|
166
|
+
structured: { table, mode, groupBy, metrics: metrics.map(({ fn, column }) => ({ fn, column })), groups: out, groupsTotal, groupsReturned: out.length, rowsTotal },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// rows mode — token discipline: curated default columns, 120-char cells, loud paging footer.
|
|
170
|
+
const cols = (input.columns?.length ? input.columns : DEFAULT_ROW_COLUMNS[table]).map(c => requireCol(c, 'columns'));
|
|
171
|
+
const orderCol = input.orderBy ? requireCol(input.orderBy, 'orderBy') : null;
|
|
172
|
+
const limit = Math.min(Math.max(input.limit ?? 50, 1), 500);
|
|
173
|
+
const offset = Math.max(input.offset ?? 0, 0);
|
|
174
|
+
const sql = `SELECT ${cols.map(quoteIdent).join(', ')} ${from}${orderCol ? ` ORDER BY ${quoteIdent(orderCol)} ${orderDir}` : ''} LIMIT ${limit} OFFSET ${offset}`;
|
|
175
|
+
const raw = db.prepare(sql).all(...params);
|
|
176
|
+
let truncated = false;
|
|
177
|
+
const rows = raw.map(r => {
|
|
178
|
+
const o = {};
|
|
179
|
+
for (const c of cols) {
|
|
180
|
+
const t = truncateCell(r[c]);
|
|
181
|
+
if (t !== r[c])
|
|
182
|
+
truncated = true;
|
|
183
|
+
o[c] = t;
|
|
184
|
+
}
|
|
185
|
+
return o;
|
|
186
|
+
});
|
|
187
|
+
const first = rows.length ? offset + 1 : 0;
|
|
188
|
+
const last = offset + rows.length;
|
|
189
|
+
const nextOffset = last < rowsTotal ? last : null;
|
|
190
|
+
const footer = `Showing ${first}–${last} of ${rowsTotal.toLocaleString('en-US')}${nextOffset != null ? `; next offset ${nextOffset}` : ' (end)'}.` +
|
|
191
|
+
(truncated ? ` Cells over ${CELL_CAP} chars truncated (…) — use mode:aggregate or fewer columns for full values via SQL.` : '');
|
|
192
|
+
const header = `| ${cols.join(' | ')} |\n|${cols.map(() => '---').join('|')}|`;
|
|
193
|
+
const body = rows.map(r => `| ${cols.map(c => mdCell(r[c])).join(' | ')} |`).join('\n');
|
|
194
|
+
const markdown = rows.length ? `${header}\n${body}\n\n${footer}` : `No rows match (${rowsTotal} total in ${table} for these filters).`;
|
|
195
|
+
return {
|
|
196
|
+
markdown,
|
|
197
|
+
structured: { table, mode, columns: cols, rows, rowsReturned: rows.length, rowsTotal, offset, nextOffset, cellsTruncated: truncated },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=queryData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryData.js","sourceRoot":"","sources":["../../src/core/queryData.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Relevance logits for one query against many passages (batched). Higher = more relevant. */
|
|
2
|
+
export declare function scorePassages(query: string, passages: string[]): Promise<number[]>;
|
|
3
|
+
/** The single best passage's score + its index — the "max-passage" relevance of a doc to a query. */
|
|
4
|
+
export declare function maxPassageScore(query: string, passages: string[]): Promise<{
|
|
5
|
+
max: number;
|
|
6
|
+
index: number;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=reranker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.d.ts","sourceRoot":"","sources":["../../src/core/reranker.ts"],"names":[],"mappings":"AA6CA,8FAA8F;AAC9F,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYxF;AAED,qGAAqG;AACrG,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAKhH"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { env, AutoTokenizer, AutoModelForSequenceClassification } from '@huggingface/transformers';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
// Cross-encoder reranker (ms-marco-MiniLM-L-6-v2) run locally via transformers.js / ONNX — pure Node,
|
|
5
|
+
// no Python. It SCORES (query, passage) relevance; it does not generate text, so it cannot hallucinate.
|
|
6
|
+
// Model is downloaded once from the HF hub and cached on disk (persists across npm reinstalls).
|
|
7
|
+
env.cacheDir = path.join(homedir(), '.cache', 'seo-audit-console-models');
|
|
8
|
+
env.allowLocalModels = false;
|
|
9
|
+
const MODEL = 'Xenova/ms-marco-MiniLM-L-6-v2';
|
|
10
|
+
let loading = null;
|
|
11
|
+
// Prefer the GPU when present. onnxruntime-node bundles the DirectML provider (any DX12 GPU on
|
|
12
|
+
// Windows — incl. NVIDIA), not the CUDA EP, so 'dml' is the GPU path; fall back to CPU if it won't
|
|
13
|
+
// init. (mirrors ai-detect's `cuda if available else cpu`, adapted to our ONNX runtime). Override
|
|
14
|
+
// with SAC_RERANK_DEVICE=cpu|dml. fp32 — DirectML doesn't run the q8-quantised graph.
|
|
15
|
+
const DEVICE_ORDER = process.env.SAC_RERANK_DEVICE
|
|
16
|
+
? [process.env.SAC_RERANK_DEVICE]
|
|
17
|
+
: ['dml', 'cpu'];
|
|
18
|
+
/** Lazy singleton — first call downloads (~25MB) + warms the model on the best device; later calls reuse it. */
|
|
19
|
+
function load() {
|
|
20
|
+
if (!loading) {
|
|
21
|
+
loading = (async () => {
|
|
22
|
+
const tokenizer = await AutoTokenizer.from_pretrained(MODEL);
|
|
23
|
+
for (let i = 0; i < DEVICE_ORDER.length; i++) {
|
|
24
|
+
const device = DEVICE_ORDER[i];
|
|
25
|
+
try {
|
|
26
|
+
const model = await AutoModelForSequenceClassification.from_pretrained(MODEL, { device: device, dtype: 'fp32' });
|
|
27
|
+
console.error(`[reranker] ms-marco-MiniLM loaded on ${device}`);
|
|
28
|
+
return { tokenizer, model, device };
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
const last = i === DEVICE_ORDER.length - 1;
|
|
32
|
+
console.error(`[reranker] device '${device}' unavailable${last ? '' : ', falling back'}: ${e instanceof Error ? e.message.slice(0, 100) : e}`);
|
|
33
|
+
if (last)
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error('reranker: no usable device');
|
|
38
|
+
})();
|
|
39
|
+
}
|
|
40
|
+
return loading;
|
|
41
|
+
}
|
|
42
|
+
/** Relevance logits for one query against many passages (batched). Higher = more relevant. */
|
|
43
|
+
export async function scorePassages(query, passages) {
|
|
44
|
+
if (!passages.length || !query.trim())
|
|
45
|
+
return [];
|
|
46
|
+
const { tokenizer, model } = await load();
|
|
47
|
+
const out = [];
|
|
48
|
+
const BATCH = 16; // bound peak memory
|
|
49
|
+
for (let i = 0; i < passages.length; i += BATCH) {
|
|
50
|
+
const batch = passages.slice(i, i + BATCH);
|
|
51
|
+
const inputs = tokenizer(Array(batch.length).fill(query), { text_pair: batch, padding: true, truncation: true });
|
|
52
|
+
const { logits } = await model(inputs);
|
|
53
|
+
for (const row of logits.tolist())
|
|
54
|
+
out.push(row[0]);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** The single best passage's score + its index — the "max-passage" relevance of a doc to a query. */
|
|
59
|
+
export async function maxPassageScore(query, passages) {
|
|
60
|
+
const scores = await scorePassages(query, passages);
|
|
61
|
+
let max = -Infinity, index = -1;
|
|
62
|
+
scores.forEach((v, i) => { if (v > max) {
|
|
63
|
+
max = v;
|
|
64
|
+
index = i;
|
|
65
|
+
} });
|
|
66
|
+
return { max, index };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=reranker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.js","sourceRoot":"","sources":["../../src/core/reranker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* robots.txt politeness gate. Honours both Disallow AND Allow with longest-prefix-match
|
|
3
|
+
* (most specific path wins; Allow wins ties) — so a broad `Disallow: /` paired with
|
|
4
|
+
* `Allow: /public/` doesn't wrongly empty the crawl. NB: prefix match only — `*`/`$`
|
|
5
|
+
* wildcard patterns (e.g. `Disallow: /*?`) are not yet interpreted (owned-site crawl).
|
|
6
|
+
*/
|
|
7
|
+
export interface RobotsRules {
|
|
8
|
+
isAllowed(pathname: string): boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function fetchRobots(origin: string, userAgent: string, uaToken?: string): Promise<RobotsRules>;
|
|
11
|
+
//# sourceMappingURL=robots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AA0DD,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,SAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAWxH"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const ALLOW_ALL = { isAllowed: () => true };
|
|
2
|
+
function parseRules(txt, uaToken) {
|
|
3
|
+
const groups = [];
|
|
4
|
+
let current = null;
|
|
5
|
+
let lastWasAgent = false;
|
|
6
|
+
for (const raw of txt.split(/\r?\n/)) {
|
|
7
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
8
|
+
if (!line)
|
|
9
|
+
continue;
|
|
10
|
+
const idx = line.indexOf(':');
|
|
11
|
+
if (idx === -1)
|
|
12
|
+
continue;
|
|
13
|
+
const field = line.slice(0, idx).trim().toLowerCase();
|
|
14
|
+
const value = line.slice(idx + 1).trim();
|
|
15
|
+
if (field === 'user-agent') {
|
|
16
|
+
if (!current || !lastWasAgent) {
|
|
17
|
+
current = { agents: [], rules: [] };
|
|
18
|
+
groups.push(current);
|
|
19
|
+
}
|
|
20
|
+
current.agents.push(value.toLowerCase());
|
|
21
|
+
lastWasAgent = true;
|
|
22
|
+
}
|
|
23
|
+
else if ((field === 'disallow' || field === 'allow') && current) {
|
|
24
|
+
// An empty Disallow value means "allow everything" — carries no restriction.
|
|
25
|
+
if (value)
|
|
26
|
+
current.rules.push({ path: value, allow: field === 'allow' });
|
|
27
|
+
lastWasAgent = false;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
lastWasAgent = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// RFC 9309 §2.2.1: only the MOST SPECIFIC matching group applies — a bot-specific group
|
|
34
|
+
// replaces the `*` group entirely (it does not merge with it). Otherwise a site that
|
|
35
|
+
// whitelists this bot (`User-agent: seo-audit-console / Allow: /`) alongside a broad
|
|
36
|
+
// `User-agent: * / Disallow: /` would still be blocked. Groups at equal specificity merge.
|
|
37
|
+
const ua = uaToken.toLowerCase();
|
|
38
|
+
const agentScore = (a) => (a === '*' ? 0 : ua.includes(a) ? a.length : -1);
|
|
39
|
+
const groupScore = (g) => Math.max(-1, ...g.agents.map(agentScore));
|
|
40
|
+
const best = Math.max(-1, ...groups.map(groupScore));
|
|
41
|
+
if (best < 0)
|
|
42
|
+
return [];
|
|
43
|
+
return groups.filter(g => groupScore(g) === best).flatMap(g => g.rules);
|
|
44
|
+
}
|
|
45
|
+
function makeRules(rules) {
|
|
46
|
+
if (!rules.length)
|
|
47
|
+
return ALLOW_ALL;
|
|
48
|
+
return {
|
|
49
|
+
isAllowed(p) {
|
|
50
|
+
let best = null;
|
|
51
|
+
for (const r of rules) {
|
|
52
|
+
if (!p.startsWith(r.path))
|
|
53
|
+
continue;
|
|
54
|
+
// longest match wins; Allow beats Disallow at equal length
|
|
55
|
+
if (!best || r.path.length > best.path.length || (r.path.length === best.path.length && r.allow))
|
|
56
|
+
best = r;
|
|
57
|
+
}
|
|
58
|
+
return best ? best.allow : true;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export async function fetchRobots(origin, userAgent, uaToken = 'seo-audit-console') {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(new URL('/robots.txt', origin), {
|
|
65
|
+
headers: { 'user-agent': userAgent, 'cache-control': 'no-cache' },
|
|
66
|
+
signal: AbortSignal.timeout(10000),
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
return ALLOW_ALL;
|
|
70
|
+
return makeRules(parseRules(await res.text(), uaToken));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return ALLOW_ALL; // robots unavailable → don't block (owned-site crawl)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=robots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/core/robots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type UrlKeyOptions } from './url-key.js';
|
|
2
|
+
export interface SitemapResult {
|
|
3
|
+
urls: {
|
|
4
|
+
urlKey: string;
|
|
5
|
+
url: string;
|
|
6
|
+
lastmod: string | null;
|
|
7
|
+
}[];
|
|
8
|
+
sitemapsFetched: number;
|
|
9
|
+
sources: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function fetchSitemapUrls(origin: string, ua: string, keyOpts: UrlKeyOptions, opts?: {
|
|
12
|
+
maxSitemaps?: number;
|
|
13
|
+
maxUrls?: number;
|
|
14
|
+
}): Promise<SitemapResult>;
|
|
15
|
+
//# sourceMappingURL=sitemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/core/sitemap.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAiC1D,MAAM,WAAW,aAAa;IAAG,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE;AAE9I,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACxG,OAAO,CAAC,aAAa,CAAC,CAgDxB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { gunzipSync } from 'node:zlib';
|
|
2
|
+
import { urlKey } from './url-key.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sitemap discovery + parse for reconciliation against the crawl. Finds sitemaps via robots.txt
|
|
5
|
+
* `Sitemap:` directives (falling back to /sitemap.xml), follows <sitemapindex> children, handles
|
|
6
|
+
* gzipped sitemaps, and returns the listed URLs normalised to url_key. Bounded (count + timeout)
|
|
7
|
+
* so a giant sitemap set can't stall a crawl.
|
|
8
|
+
*/
|
|
9
|
+
const LOC = /<loc>\s*([^<\s]+?)\s*<\/loc>/gi;
|
|
10
|
+
// XML sitemaps legally escape URL characters (?a=1&b=2 is stored as ?a=1&b=2). Decode
|
|
11
|
+
// numeric + the five predefined entities so stored URLs match crawl/GSC url_keys and the
|
|
12
|
+
// crawler seeds fetch the real URL, not the literal `&` form. `&` is decoded last
|
|
13
|
+
// so `&lt;` correctly yields `<` rather than `<`.
|
|
14
|
+
const decodeXmlEntities = (s) => s
|
|
15
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)))
|
|
16
|
+
.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)))
|
|
17
|
+
.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'")
|
|
18
|
+
.replace(/&/g, '&');
|
|
19
|
+
async function fetchText(url, ua) {
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(url, { headers: { 'user-agent': ua }, signal: AbortSignal.timeout(15000) });
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
return null;
|
|
24
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
25
|
+
const ct = (res.headers.get('content-type') ?? '').toLowerCase();
|
|
26
|
+
if (url.toLowerCase().endsWith('.gz') || ct.includes('gzip')) {
|
|
27
|
+
try {
|
|
28
|
+
return gunzipSync(buf).toString('utf8');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return buf.toString('utf8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return buf.toString('utf8');
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function fetchSitemapUrls(origin, ua, keyOpts, opts = {}) {
|
|
41
|
+
const maxSitemaps = opts.maxSitemaps ?? 60;
|
|
42
|
+
const maxUrls = opts.maxUrls ?? 100000;
|
|
43
|
+
// Discover sitemap locations from robots.txt; fall back to /sitemap.xml.
|
|
44
|
+
const sources = [];
|
|
45
|
+
const robots = await fetchText(new URL('/robots.txt', origin).toString(), ua);
|
|
46
|
+
if (robots)
|
|
47
|
+
for (const m of robots.matchAll(/^\s*sitemap:\s*(\S+)/gim))
|
|
48
|
+
sources.push(m[1].trim());
|
|
49
|
+
if (!sources.length)
|
|
50
|
+
sources.push(new URL('/sitemap.xml', origin).toString());
|
|
51
|
+
const out = new Map(); // urlKey → entry (dedup)
|
|
52
|
+
const queue = [...new Set(sources)];
|
|
53
|
+
let fetched = 0;
|
|
54
|
+
const URL_BLOCK = /<url>([\s\S]*?)<\/url>/gi;
|
|
55
|
+
const LOC_ONE = /<loc>\s*([^<\s]+?)\s*<\/loc>/i;
|
|
56
|
+
const LASTMOD_ONE = /<lastmod>\s*([^<\s]+?)\s*<\/lastmod>/i;
|
|
57
|
+
const add = (loc, lastmod) => {
|
|
58
|
+
try {
|
|
59
|
+
const k = urlKey(loc, keyOpts);
|
|
60
|
+
if (!out.has(k))
|
|
61
|
+
out.set(k, { url: loc, lastmod });
|
|
62
|
+
}
|
|
63
|
+
catch { /* skip bad loc */ }
|
|
64
|
+
};
|
|
65
|
+
while (queue.length && fetched < maxSitemaps && out.size < maxUrls) {
|
|
66
|
+
const sm = queue.shift();
|
|
67
|
+
const txt = await fetchText(sm, ua);
|
|
68
|
+
fetched++;
|
|
69
|
+
if (!txt)
|
|
70
|
+
continue;
|
|
71
|
+
if (/<sitemapindex[\s>]/i.test(txt)) {
|
|
72
|
+
for (const m of txt.matchAll(LOC)) {
|
|
73
|
+
if (queue.length + fetched < maxSitemaps)
|
|
74
|
+
queue.push(decodeXmlEntities(m[1])); // child sitemap
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Parse per-<url> block so each <lastmod> claim stays paired with its <loc>.
|
|
79
|
+
let sawBlock = false;
|
|
80
|
+
for (const b of txt.matchAll(URL_BLOCK)) {
|
|
81
|
+
sawBlock = true;
|
|
82
|
+
const loc = b[1].match(LOC_ONE)?.[1];
|
|
83
|
+
if (!loc)
|
|
84
|
+
continue;
|
|
85
|
+
add(decodeXmlEntities(loc), b[1].match(LASTMOD_ONE)?.[1] ?? null);
|
|
86
|
+
if (out.size >= maxUrls)
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
// Defensive fallback for malformed sitemaps that list bare <loc>s without <url> wrappers.
|
|
90
|
+
if (!sawBlock) {
|
|
91
|
+
for (const m of txt.matchAll(LOC)) {
|
|
92
|
+
add(decodeXmlEntities(m[1]), null);
|
|
93
|
+
if (out.size >= maxUrls)
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { urls: [...out].map(([k, v]) => ({ urlKey: k, url: v.url, lastmod: v.lastmod })), sitemapsFetched: fetched, sources };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/core/sitemap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SQL fragments. There is exactly ONE definition of "is this page HTML" in SQL —
|
|
3
|
+
* keep it aligned with the runtime helper `isHtmlContentType` in Crawler.ts.
|
|
4
|
+
*/
|
|
5
|
+
export declare const HTML_CT = "(LOWER(content_type) LIKE 'text/html%' OR LOWER(content_type) LIKE 'application/xhtml+xml%')";
|
|
6
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/core/sql.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,OAAO,iGAAiG,CAAC"}
|
package/dist/core/sql.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SQL fragments. There is exactly ONE definition of "is this page HTML" in SQL —
|
|
3
|
+
* keep it aligned with the runtime helper `isHtmlContentType` in Crawler.ts.
|
|
4
|
+
*/
|
|
5
|
+
export const HTML_CT = `(LOWER(content_type) LIKE 'text/html%' OR LOWER(content_type) LIKE 'application/xhtml+xml%')`;
|
|
6
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/core/sql.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,8FAA8F,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GscProperty {
|
|
2
|
+
siteUrl: string;
|
|
3
|
+
permissionLevel?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface GscApiRow {
|
|
6
|
+
keys?: string[];
|
|
7
|
+
clicks: number;
|
|
8
|
+
impressions: number;
|
|
9
|
+
ctr: number;
|
|
10
|
+
position: number;
|
|
11
|
+
}
|
|
12
|
+
export interface FetchOptions {
|
|
13
|
+
startDate: string;
|
|
14
|
+
endDate: string;
|
|
15
|
+
dimensions?: string[];
|
|
16
|
+
rowLimit?: number;
|
|
17
|
+
dataState?: string;
|
|
18
|
+
searchType?: string;
|
|
19
|
+
fullResync?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type JobState = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|