@se-studio/site-check 1.0.76 → 1.1.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/CHANGELOG.md +12 -0
- package/dist/screaming-frog/audit.d.ts +9 -0
- package/dist/screaming-frog/audit.d.ts.map +1 -0
- package/dist/screaming-frog/audit.js +254 -0
- package/dist/screaming-frog/audit.js.map +1 -0
- package/dist/screaming-frog/compare-report.d.ts +22 -0
- package/dist/screaming-frog/compare-report.d.ts.map +1 -0
- package/dist/screaming-frog/compare-report.js +142 -0
- package/dist/screaming-frog/compare-report.js.map +1 -0
- package/dist/screaming-frog/compare.d.ts +5 -0
- package/dist/screaming-frog/compare.d.ts.map +1 -0
- package/dist/screaming-frog/compare.js +42 -0
- package/dist/screaming-frog/compare.js.map +1 -0
- package/dist/screaming-frog/crawl-state.d.ts +19 -0
- package/dist/screaming-frog/crawl-state.d.ts.map +1 -0
- package/dist/screaming-frog/crawl-state.js +30 -0
- package/dist/screaming-frog/crawl-state.js.map +1 -0
- package/dist/screaming-frog/crawl.d.ts +6 -0
- package/dist/screaming-frog/crawl.d.ts.map +1 -0
- package/dist/screaming-frog/crawl.js +76 -0
- package/dist/screaming-frog/crawl.js.map +1 -0
- package/dist/screaming-frog/csv.d.ts +6 -0
- package/dist/screaming-frog/csv.d.ts.map +1 -0
- package/dist/screaming-frog/csv.js +71 -0
- package/dist/screaming-frog/csv.js.map +1 -0
- package/dist/screaming-frog/env.d.ts +4 -0
- package/dist/screaming-frog/env.d.ts.map +1 -0
- package/dist/screaming-frog/env.js +53 -0
- package/dist/screaming-frog/env.js.map +1 -0
- package/dist/screaming-frog/index.d.ts +15 -0
- package/dist/screaming-frog/index.d.ts.map +1 -0
- package/dist/screaming-frog/index.js +15 -0
- package/dist/screaming-frog/index.js.map +1 -0
- package/dist/screaming-frog/list-crawls.d.ts +22 -0
- package/dist/screaming-frog/list-crawls.d.ts.map +1 -0
- package/dist/screaming-frog/list-crawls.js +107 -0
- package/dist/screaming-frog/list-crawls.js.map +1 -0
- package/dist/screaming-frog/load-config.d.ts +9 -0
- package/dist/screaming-frog/load-config.d.ts.map +1 -0
- package/dist/screaming-frog/load-config.js +86 -0
- package/dist/screaming-frog/load-config.js.map +1 -0
- package/dist/screaming-frog/load-profile.d.ts +9 -0
- package/dist/screaming-frog/load-profile.d.ts.map +1 -0
- package/dist/screaming-frog/load-profile.js +32 -0
- package/dist/screaming-frog/load-profile.js.map +1 -0
- package/dist/screaming-frog/paths.d.ts +5 -0
- package/dist/screaming-frog/paths.d.ts.map +1 -0
- package/dist/screaming-frog/paths.js +34 -0
- package/dist/screaming-frog/paths.js.map +1 -0
- package/dist/screaming-frog/rules.d.ts +14 -0
- package/dist/screaming-frog/rules.d.ts.map +1 -0
- package/dist/screaming-frog/rules.js +211 -0
- package/dist/screaming-frog/rules.js.map +1 -0
- package/dist/screaming-frog/sf-runner.d.ts +12 -0
- package/dist/screaming-frog/sf-runner.d.ts.map +1 -0
- package/dist/screaming-frog/sf-runner.js +37 -0
- package/dist/screaming-frog/sf-runner.js.map +1 -0
- package/dist/screaming-frog/types.d.ts +136 -0
- package/dist/screaming-frog/types.d.ts.map +1 -0
- package/dist/screaming-frog/types.js +2 -0
- package/dist/screaming-frog/types.js.map +1 -0
- package/dist/screaming-frog-cli.d.ts +4 -0
- package/dist/screaming-frog-cli.d.ts.map +1 -0
- package/dist/screaming-frog-cli.js +326 -0
- package/dist/screaming-frog-cli.js.map +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add Screaming Frog crawl, audit, native compare, and list-crawls CLI (`site-check-screaming-frog`) driven by per-repo `seo/screaming-frog.json`. Add `screaming-frog-audit` agent skill for deployment compare workflows.
|
|
8
|
+
|
|
9
|
+
## 1.0.77
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Bulk version bump: patch for all packages
|
|
14
|
+
|
|
3
15
|
## 1.0.76
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScreamingFrogAuditCliOptions, ScreamingFrogAuditConfig, ScreamingFrogAuditResult } from './types.js';
|
|
2
|
+
export declare function runScreamingFrogAudit(config: ScreamingFrogAuditConfig): Promise<ScreamingFrogAuditResult>;
|
|
3
|
+
export declare function buildScreamingFrogMarkdownReport(result: ScreamingFrogAuditResult): string;
|
|
4
|
+
export declare function writeScreamingFrogAuditOutputs(result: ScreamingFrogAuditResult, outputDir: string, outputMdName?: string): Promise<{
|
|
5
|
+
mdPath: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function getScreamingFrogAuditExitCode(result: ScreamingFrogAuditResult): number;
|
|
8
|
+
export declare function parseScreamingFrogAuditArgv(argv: string[]): Omit<ScreamingFrogAuditCliOptions, 'site'>;
|
|
9
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/screaming-frog/audit.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EAGzB,MAAM,YAAY,CAAC;AAsEpB,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CA0GnC;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CAoFzF;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,wBAAwB,EAChC,SAAS,EAAE,MAAM,EACjB,YAAY,SAA4B,GACvC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAM7B;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CAItF;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EAAE,GACb,IAAI,CAAC,4BAA4B,EAAE,MAAM,CAAC,CA2C5C"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizeCsvCell, parseCsv, parseCsvRecords } from './csv.js';
|
|
4
|
+
import { classifyScreamingFrogIssue, getIssueRulesForSite, normalizeIssueKey, TIER_LABELS, TIER_ORDER, } from './rules.js';
|
|
5
|
+
async function readCsvFile(filePath) {
|
|
6
|
+
const raw = await readFile(filePath, 'utf8');
|
|
7
|
+
return parseCsvRecords(raw);
|
|
8
|
+
}
|
|
9
|
+
async function findIssueReportFile(exportDir, issueName) {
|
|
10
|
+
const issuesDir = path.join(exportDir, 'issues_reports');
|
|
11
|
+
const key = normalizeIssueKey(issueName);
|
|
12
|
+
let files;
|
|
13
|
+
try {
|
|
14
|
+
files = await readdir(issuesDir);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const exact = files.find((f) => f === `${key}.csv`);
|
|
20
|
+
if (exact)
|
|
21
|
+
return path.join(issuesDir, exact);
|
|
22
|
+
const relaxed = files.find((f) => normalizeIssueKey(f.replace(/\.csv$/, '')) === key);
|
|
23
|
+
if (relaxed)
|
|
24
|
+
return path.join(issuesDir, relaxed);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function loadIssueSamples(exportDir, issueName, limit) {
|
|
28
|
+
const reportPath = await findIssueReportFile(exportDir, issueName);
|
|
29
|
+
if (!reportPath)
|
|
30
|
+
return [];
|
|
31
|
+
const records = await readCsvFile(reportPath);
|
|
32
|
+
const urls = records
|
|
33
|
+
.map((r) => r.Address ?? r.URL ?? r.Source ?? '')
|
|
34
|
+
.filter((u) => u.startsWith('http'));
|
|
35
|
+
return [...new Set(urls)].slice(0, limit);
|
|
36
|
+
}
|
|
37
|
+
function parseCrawlOverview(text) {
|
|
38
|
+
const rows = parseCsv(text).map((row) => row.map(normalizeCsvCell));
|
|
39
|
+
let siteUrl = '';
|
|
40
|
+
let crawlStarted;
|
|
41
|
+
let htmlPages = 0;
|
|
42
|
+
let internalIndexable = 0;
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
const label = row[0] ?? '';
|
|
45
|
+
const count = Number.parseInt(row[1] ?? '', 10);
|
|
46
|
+
if (label === 'Site Crawled')
|
|
47
|
+
siteUrl = row[1] ?? siteUrl;
|
|
48
|
+
if (label === 'Start Date') {
|
|
49
|
+
const time = rows.find((r) => r[0] === 'Start Time')?.[1];
|
|
50
|
+
crawlStarted = `${row[1]} ${time ?? ''}`.trim();
|
|
51
|
+
}
|
|
52
|
+
if (label === 'HTML' && row[4] === 'Internal URLs' && !Number.isNaN(count)) {
|
|
53
|
+
htmlPages = count;
|
|
54
|
+
}
|
|
55
|
+
if (label === 'Total Internal Indexable URLs' && !Number.isNaN(count)) {
|
|
56
|
+
internalIndexable = count;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { siteUrl, crawlStarted, htmlPages, internalIndexable };
|
|
60
|
+
}
|
|
61
|
+
export async function runScreamingFrogAudit(config) {
|
|
62
|
+
const sampleLimit = config.sampleLimit ?? 8;
|
|
63
|
+
const exportDir = path.resolve(config.exportDir);
|
|
64
|
+
const site = config.site;
|
|
65
|
+
const siteHost = site?.siteHost ?? '';
|
|
66
|
+
const rules = config.issueRules ?? getIssueRulesForSite(site?.issueRuleOverrides);
|
|
67
|
+
const [overviewRaw, issuesRaw, redirectErrors] = await Promise.all([
|
|
68
|
+
readFile(path.join(exportDir, 'crawl_overview.csv'), 'utf8').catch(() => ''),
|
|
69
|
+
readFile(path.join(exportDir, 'issues_overview_report.csv'), 'utf8').catch(() => ''),
|
|
70
|
+
readCsvFile(path.join(exportDir, 'redirects_to_error.csv')).catch(() => []),
|
|
71
|
+
]);
|
|
72
|
+
const crawlMeta = overviewRaw
|
|
73
|
+
? parseCrawlOverview(overviewRaw)
|
|
74
|
+
: {
|
|
75
|
+
siteUrl: config.siteUrl ?? site?.defaultSiteUrl ?? '',
|
|
76
|
+
crawlStarted: undefined,
|
|
77
|
+
htmlPages: 0,
|
|
78
|
+
internalIndexable: 0,
|
|
79
|
+
};
|
|
80
|
+
const siteUrl = config.siteUrl ?? (crawlMeta.siteUrl || site?.defaultSiteUrl || '');
|
|
81
|
+
const issueRecords = issuesRaw ? parseCsvRecords(issuesRaw) : [];
|
|
82
|
+
const classified = [];
|
|
83
|
+
for (const record of issueRecords) {
|
|
84
|
+
const name = record['Issue Name'] ?? '';
|
|
85
|
+
if (!name)
|
|
86
|
+
continue;
|
|
87
|
+
const classification = classifyScreamingFrogIssue(name, rules);
|
|
88
|
+
if (!classification)
|
|
89
|
+
continue;
|
|
90
|
+
const urlCount = Number.parseInt(record.URLs ?? '0', 10) || 0;
|
|
91
|
+
const samples = await loadIssueSamples(exportDir, name, sampleLimit);
|
|
92
|
+
classified.push({
|
|
93
|
+
name,
|
|
94
|
+
sfType: record['Issue Type'] ?? '',
|
|
95
|
+
sfPriority: record['Issue Priority'] ?? '',
|
|
96
|
+
urlCount,
|
|
97
|
+
pctOfHtml: record['% of Total'] ?? '',
|
|
98
|
+
tier: classification.tier,
|
|
99
|
+
category: classification.category,
|
|
100
|
+
note: classification.note,
|
|
101
|
+
samples,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (redirectErrors.length > 0 && !classified.some((i) => i.name.includes('Redirect'))) {
|
|
105
|
+
const allSources = [
|
|
106
|
+
...new Set(redirectErrors
|
|
107
|
+
.map((r) => r.Source ?? r.Address ?? '')
|
|
108
|
+
.filter((u) => (siteHost ? u.includes(siteHost) : u.startsWith('http')))),
|
|
109
|
+
];
|
|
110
|
+
const samples = allSources.slice(0, sampleLimit);
|
|
111
|
+
classified.push({
|
|
112
|
+
name: 'Redirects: To error (report)',
|
|
113
|
+
sfType: 'Warning',
|
|
114
|
+
sfPriority: 'High',
|
|
115
|
+
urlCount: allSources.length,
|
|
116
|
+
pctOfHtml: '',
|
|
117
|
+
tier: 'p1',
|
|
118
|
+
category: 'redirects',
|
|
119
|
+
note: 'From `redirects_to_error.csv` — outbound redirect targets returning 4xx.',
|
|
120
|
+
samples,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
classified.sort((a, b) => {
|
|
124
|
+
const tierDiff = TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier);
|
|
125
|
+
if (tierDiff !== 0)
|
|
126
|
+
return tierDiff;
|
|
127
|
+
return b.urlCount - a.urlCount;
|
|
128
|
+
});
|
|
129
|
+
const tierCounts = Object.fromEntries(TIER_ORDER.map((t) => [t, 0]));
|
|
130
|
+
const tierUrlCounts = Object.fromEntries(TIER_ORDER.map((t) => [t, 0]));
|
|
131
|
+
for (const issue of classified) {
|
|
132
|
+
tierCounts[issue.tier] += 1;
|
|
133
|
+
tierUrlCounts[issue.tier] += issue.urlCount;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
exportDir,
|
|
137
|
+
exportDirLabel: path.basename(exportDir),
|
|
138
|
+
siteLabel: site?.siteLabel ?? 'Site',
|
|
139
|
+
siteUrl,
|
|
140
|
+
productionAuditDoc: site?.productionAuditDoc,
|
|
141
|
+
crawlStarted: crawlMeta.crawlStarted,
|
|
142
|
+
htmlPages: crawlMeta.htmlPages,
|
|
143
|
+
internalIndexable: crawlMeta.internalIndexable,
|
|
144
|
+
issues: classified,
|
|
145
|
+
tierCounts,
|
|
146
|
+
tierUrlCounts,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export function buildScreamingFrogMarkdownReport(result) {
|
|
150
|
+
const productionCrossCheck = result.productionAuditDoc
|
|
151
|
+
? `Cross-check template/metadata issues against [\`${result.productionAuditDoc}\`](./${result.productionAuditDoc}) (\`pnpm seo:audit:production\`).`
|
|
152
|
+
: 'Cross-check template/metadata issues with live HTML audits where available.';
|
|
153
|
+
const lines = [
|
|
154
|
+
`# ${result.siteLabel} Screaming Frog crawl audit`,
|
|
155
|
+
'',
|
|
156
|
+
`**Site:** ${result.siteUrl}`,
|
|
157
|
+
`**Export:** \`${result.exportDirLabel}\``,
|
|
158
|
+
`**Generated:** ${new Date().toISOString()}`,
|
|
159
|
+
...(result.crawlStarted ? [`**Crawl started:** ${result.crawlStarted}`] : []),
|
|
160
|
+
'',
|
|
161
|
+
'## Crawl summary',
|
|
162
|
+
'',
|
|
163
|
+
'| Metric | Count |',
|
|
164
|
+
'|--------|------:|',
|
|
165
|
+
`| Internal HTML (2xx) | ${result.htmlPages} |`,
|
|
166
|
+
`| Internal indexable URLs | ${result.internalIndexable} |`,
|
|
167
|
+
'',
|
|
168
|
+
productionCrossCheck,
|
|
169
|
+
'',
|
|
170
|
+
'## Prioritized issues',
|
|
171
|
+
'',
|
|
172
|
+
'| Tier | Issue types | URL rows (sum) |',
|
|
173
|
+
'|------|------------:|---------------:|',
|
|
174
|
+
...TIER_ORDER.filter((t) => (result.tierCounts[t] ?? 0) > 0).map((t) => `| ${TIER_LABELS[t]} | ${result.tierCounts[t]} | ${result.tierUrlCounts[t]} |`),
|
|
175
|
+
'',
|
|
176
|
+
];
|
|
177
|
+
for (const tier of TIER_ORDER) {
|
|
178
|
+
const bucket = result.issues.filter((i) => i.tier === tier);
|
|
179
|
+
if (bucket.length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
lines.push(`## ${TIER_LABELS[tier]}`, '');
|
|
182
|
+
for (const issue of bucket) {
|
|
183
|
+
lines.push(`### ${issue.name}`, '', '| Field | Value |', '|-------|-------|', `| Screaming Frog type | ${issue.sfType} (${issue.sfPriority}) |`, `| URLs | ${issue.urlCount} (${issue.pctOfHtml}% of HTML) |`, `| Category | ${issue.category} |`, ...(issue.note ? [`| Note | ${issue.note} |`] : []), '');
|
|
184
|
+
if (issue.samples.length > 0) {
|
|
185
|
+
lines.push('Sample URLs:', '');
|
|
186
|
+
for (const url of issue.samples) {
|
|
187
|
+
lines.push(`- ${url}`);
|
|
188
|
+
}
|
|
189
|
+
if (issue.urlCount > issue.samples.length) {
|
|
190
|
+
lines.push(`- _…and ${issue.urlCount - issue.samples.length} more in export_`);
|
|
191
|
+
}
|
|
192
|
+
lines.push('');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
lines.push('## Recommended next steps', '', '1. **P0/P1 response codes** — Fix broken internal pages (404/410) and redirect-to-error chains.', '2. **P1 indexing** — Resolve sitemap vs `noindex` mismatches; confirm intentional noindex pages.', '3. **P1 metadata** — Fix pages missing meta descriptions or titles (see samples above).', '4. **P1 structured data** — Fix Schema.org required-field failures; cross-check rendered JSON-LD.', '5. **P2 sitemap** — Reconcile orphan / not-in-sitemap URLs with internal linking.', '', 'Re-run after fixes:', '', '```bash', 'pnpm seo:screaming-frog:crawl', 'pnpm seo:screaming-frog ~/spider/exports/<timestamp>', ...(result.productionAuditDoc ? ['pnpm seo:audit:production'] : []), '```', '');
|
|
197
|
+
return lines.join('\n');
|
|
198
|
+
}
|
|
199
|
+
export async function writeScreamingFrogAuditOutputs(result, outputDir, outputMdName = 'screaming-frog-audit.md') {
|
|
200
|
+
const { mkdir, writeFile } = await import('node:fs/promises');
|
|
201
|
+
await mkdir(outputDir, { recursive: true });
|
|
202
|
+
const mdPath = path.join(outputDir, outputMdName);
|
|
203
|
+
await writeFile(mdPath, buildScreamingFrogMarkdownReport(result), 'utf8');
|
|
204
|
+
return { mdPath };
|
|
205
|
+
}
|
|
206
|
+
export function getScreamingFrogAuditExitCode(result) {
|
|
207
|
+
const p0 = result.issues.filter((i) => i.tier === 'p0').length;
|
|
208
|
+
const p1 = result.issues.filter((i) => i.tier === 'p1' && i.urlCount > 0).length;
|
|
209
|
+
return p0 > 0 || p1 > 0 ? 1 : 0;
|
|
210
|
+
}
|
|
211
|
+
export function parseScreamingFrogAuditArgv(argv) {
|
|
212
|
+
const args = argv.slice(2);
|
|
213
|
+
let exportDir = '';
|
|
214
|
+
let outDir;
|
|
215
|
+
let siteUrl;
|
|
216
|
+
let sampleLimit;
|
|
217
|
+
for (let i = 0; i < args.length; i++) {
|
|
218
|
+
const arg = args[i];
|
|
219
|
+
if (!arg)
|
|
220
|
+
continue;
|
|
221
|
+
if (arg === '--export-dir') {
|
|
222
|
+
const value = args[++i];
|
|
223
|
+
if (value)
|
|
224
|
+
exportDir = value;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (arg === '--out-dir') {
|
|
228
|
+
const value = args[++i];
|
|
229
|
+
if (value)
|
|
230
|
+
outDir = value;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (arg === '--site-url') {
|
|
234
|
+
const value = args[++i];
|
|
235
|
+
if (value)
|
|
236
|
+
siteUrl = value;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (arg === '--sample-limit') {
|
|
240
|
+
const value = args[++i];
|
|
241
|
+
if (value)
|
|
242
|
+
sampleLimit = Number.parseInt(value, 10);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (!arg.startsWith('-') && !exportDir) {
|
|
246
|
+
exportDir = arg;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!exportDir) {
|
|
250
|
+
throw new Error('Missing Screaming Frog export directory. Pass as positional arg or --export-dir <path>.');
|
|
251
|
+
}
|
|
252
|
+
return { exportDir, outDir, siteUrl, sampleLimit };
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/screaming-frog/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC;AASpB,KAAK,UAAU,WAAW,CAAC,QAAgB;IACzC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,SAAiB,EAAE,SAAiB;IACrE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,GAAG,MAAM,CAAC,CAAC;IACpD,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IACtF,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,SAAiB,EACjB,SAAiB,EACjB,KAAa;IAEb,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAE3B,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO;SACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;SAChD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY;IAMtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACpE,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,YAAgC,CAAC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAE1B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAEhD,IAAI,KAAK,KAAK,cAAc;YAAE,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;QAC1D,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC1D,YAAY,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,eAAe,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3E,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;QACD,IAAI,KAAK,KAAK,+BAA+B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACtE,iBAAiB,GAAG,KAAK,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAgC;IAEhC,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,IAAI,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAElF,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAC5E,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,4BAA4B,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QACpF,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,CAC/D,GAAG,EAAE,CAAC,EAA8B,CACrC;KACF,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,WAAW;QAC3B,CAAC,CAAC,kBAAkB,CAAC,WAAW,CAAC;QACjC,CAAC,CAAC;YACE,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI,EAAE,cAAc,IAAI,EAAE;YACrD,YAAY,EAAE,SAAS;YACvB,SAAS,EAAE,CAAC;YACZ,iBAAiB,EAAE,CAAC;SACrB,CAAC;IACN,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC;IAEpF,MAAM,YAAY,GAAG,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,MAAM,UAAU,GAA4B,EAAE,CAAC;IAE/C,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,cAAc,GAAG,0BAA0B,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/D,IAAI,CAAC,cAAc;YAAE,SAAS;QAE9B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAErE,UAAU,CAAC,IAAI,CAAC;YACd,IAAI;YACJ,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;YAClC,UAAU,EAAE,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE;YAC1C,QAAQ;YACR,SAAS,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;YACrC,IAAI,EAAE,cAAc,CAAC,IAAI;YACzB,QAAQ,EAAE,cAAc,CAAC,QAAQ;YACjC,IAAI,EAAE,cAAc,CAAC,IAAI;YACzB,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QACtF,MAAM,UAAU,GAAG;YACjB,GAAG,IAAI,GAAG,CACR,cAAc;iBACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;iBACvC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAC3E;SACF,CAAC;QACF,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAEjD,UAAU,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,SAAS;YACjB,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,UAAU,CAAC,MAAM;YAC3B,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,0EAA0E;YAChF,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzE,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QACpC,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAGlE,CAAC;IACF,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAGrE,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,SAAS;QACT,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACxC,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,MAAM;QACpC,OAAO;QACP,kBAAkB,EAAE,IAAI,EAAE,kBAAkB;QAC5C,YAAY,EAAE,SAAS,CAAC,YAAY;QACpC,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,iBAAiB,EAAE,SAAS,CAAC,iBAAiB;QAC9C,MAAM,EAAE,UAAU;QAClB,UAAU;QACV,aAAa;KACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,MAAgC;IAC/E,MAAM,oBAAoB,GAAG,MAAM,CAAC,kBAAkB;QACpD,CAAC,CAAC,mDAAmD,MAAM,CAAC,kBAAkB,SAAS,MAAM,CAAC,kBAAkB,oCAAoC;QACpJ,CAAC,CAAC,6EAA6E,CAAC;IAElF,MAAM,KAAK,GAAa;QACtB,KAAK,MAAM,CAAC,SAAS,6BAA6B;QAClD,EAAE;QACF,aAAa,MAAM,CAAC,OAAO,EAAE;QAC7B,iBAAiB,MAAM,CAAC,cAAc,IAAI;QAC1C,kBAAkB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE;QAC5C,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,sBAAsB,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,EAAE;QACF,kBAAkB;QAClB,EAAE;QACF,oBAAoB;QACpB,oBAAoB;QACpB,2BAA2B,MAAM,CAAC,SAAS,IAAI;QAC/C,+BAA+B,MAAM,CAAC,iBAAiB,IAAI;QAC3D,EAAE;QACF,oBAAoB;QACpB,EAAE;QACF,uBAAuB;QACvB,EAAE;QACF,yCAAyC;QACzC,yCAAyC;QACzC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAC9D,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CACtF;QACD,EAAE;KACH,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAElC,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAE1C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CACR,OAAO,KAAK,CAAC,IAAI,EAAE,EACnB,EAAE,EACF,mBAAmB,EACnB,mBAAmB,EACnB,2BAA2B,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,KAAK,EACjE,YAAY,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,SAAS,cAAc,EAC5D,gBAAgB,KAAK,CAAC,QAAQ,IAAI,EAClC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EACnD,EAAE,CACH,CAAC;YAEF,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBAC/B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBAChC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;gBACzB,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;oBAC1C,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,kBAAkB,CAAC,CAAC;gBACjF,CAAC;gBACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CACR,2BAA2B,EAC3B,EAAE,EACF,iGAAiG,EACjG,kGAAkG,EAClG,yFAAyF,EACzF,mGAAmG,EACnG,mFAAmF,EACnF,EAAE,EACF,qBAAqB,EACrB,EAAE,EACF,SAAS,EACT,+BAA+B,EAC/B,sDAAsD,EACtD,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EACnE,KAAK,EACL,EAAE,CACH,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,MAAgC,EAChC,SAAiB,EACjB,YAAY,GAAG,yBAAyB;IAExC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAC9D,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAClD,MAAM,SAAS,CAAC,MAAM,EAAE,gCAAgC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,MAAgC;IAC5E,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IACjF,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,IAAc;IAEd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,MAA0B,CAAC;IAC/B,IAAI,OAA2B,CAAC;IAChC,IAAI,WAA+B,CAAC;IAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,KAAK;gBAAE,SAAS,GAAG,KAAK,CAAC;YAC7B,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,KAAK;gBAAE,MAAM,GAAG,KAAK,CAAC;YAC1B,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,KAAK;gBAAE,OAAO,GAAG,KAAK,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,KAAK;gBAAE,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpD,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,SAAS,GAAG,GAAG,CAAC;QAClB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScreamingFrogCompareResult } from './types.js';
|
|
2
|
+
export interface ScreamingFrogCompareMetric {
|
|
3
|
+
label: string;
|
|
4
|
+
changed: number;
|
|
5
|
+
samples: Array<{
|
|
6
|
+
url: string;
|
|
7
|
+
before: string;
|
|
8
|
+
after: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface ScreamingFrogCompareAnalysis {
|
|
12
|
+
matchedUrls: number;
|
|
13
|
+
metrics: ScreamingFrogCompareMetric[];
|
|
14
|
+
significantChanges: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function analyzeScreamingFrogCompare(exportDir: string, sampleLimit?: number): Promise<ScreamingFrogCompareAnalysis>;
|
|
17
|
+
export declare function buildScreamingFrogCompareMarkdownReport(result: ScreamingFrogCompareResult, analysis: ScreamingFrogCompareAnalysis): string;
|
|
18
|
+
export declare function writeScreamingFrogCompareOutputs(result: ScreamingFrogCompareResult, analysis: ScreamingFrogCompareAnalysis, outputDir: string, outputMdName: string): Promise<{
|
|
19
|
+
mdPath: string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function getScreamingFrogCompareExitCode(analysis: ScreamingFrogCompareAnalysis): number;
|
|
22
|
+
//# sourceMappingURL=compare-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-report.d.ts","sourceRoot":"","sources":["../../src/screaming-frog/compare-report.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,0BAA0B,EAAE,CAAC;IACtC,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAsED,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,WAAW,SAAI,GACd,OAAO,CAAC,4BAA4B,CAAC,CAmDvC;AAED,wBAAgB,uCAAuC,CACrD,MAAM,EAAE,0BAA0B,EAClC,QAAQ,EAAE,4BAA4B,GACrC,MAAM,CAiER;AAED,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,0BAA0B,EAClC,QAAQ,EAAE,4BAA4B,EACtC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAM7B;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,4BAA4B,GAAG,MAAM,CAQ9F"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseCsvRecords } from './csv.js';
|
|
4
|
+
function changedField(records, currentKey, previousKey, label, sampleLimit) {
|
|
5
|
+
const samples = [];
|
|
6
|
+
let changed = 0;
|
|
7
|
+
for (const record of records) {
|
|
8
|
+
const current = (record[currentKey] ?? '').trim();
|
|
9
|
+
const previous = (record[previousKey] ?? '').trim();
|
|
10
|
+
if (!current && !previous)
|
|
11
|
+
continue;
|
|
12
|
+
if (current === previous)
|
|
13
|
+
continue;
|
|
14
|
+
changed += 1;
|
|
15
|
+
if (samples.length < sampleLimit) {
|
|
16
|
+
samples.push({
|
|
17
|
+
url: record.Address ?? record.URL ?? '',
|
|
18
|
+
before: previous || '—',
|
|
19
|
+
after: current || '—',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { label, changed, samples };
|
|
24
|
+
}
|
|
25
|
+
function changedNumericField(records, currentKey, previousKey, label, threshold, sampleLimit) {
|
|
26
|
+
const samples = [];
|
|
27
|
+
let changed = 0;
|
|
28
|
+
for (const record of records) {
|
|
29
|
+
const current = Number.parseInt(record[currentKey] ?? '', 10);
|
|
30
|
+
const previous = Number.parseInt(record[previousKey] ?? '', 10);
|
|
31
|
+
if (Number.isNaN(current) || Number.isNaN(previous))
|
|
32
|
+
continue;
|
|
33
|
+
const delta = Math.abs(current - previous);
|
|
34
|
+
const pct = previous === 0 ? (delta > 0 ? 100 : 0) : (delta / previous) * 100;
|
|
35
|
+
if (delta < threshold && pct < 10)
|
|
36
|
+
continue;
|
|
37
|
+
changed += 1;
|
|
38
|
+
if (samples.length < sampleLimit) {
|
|
39
|
+
samples.push({
|
|
40
|
+
url: record.Address ?? record.URL ?? '',
|
|
41
|
+
before: String(previous),
|
|
42
|
+
after: String(current),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { label, changed, samples };
|
|
47
|
+
}
|
|
48
|
+
async function readCsvRecords(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
const raw = await readFile(filePath, 'utf8');
|
|
51
|
+
return parseCsvRecords(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function analyzeScreamingFrogCompare(exportDir, sampleLimit = 8) {
|
|
58
|
+
const changeDetectionPath = path.join(exportDir, 'change_detection_all.csv');
|
|
59
|
+
const responseCodesPath = path.join(exportDir, 'response_codes_all.csv');
|
|
60
|
+
const records = await readCsvRecords(changeDetectionPath);
|
|
61
|
+
const responseRecords = await readCsvRecords(responseCodesPath);
|
|
62
|
+
// SF Change Detection exports only include changed URLs — use response codes for match totals.
|
|
63
|
+
const matchedUrls = responseRecords.length;
|
|
64
|
+
if (matchedUrls === 0) {
|
|
65
|
+
return {
|
|
66
|
+
matchedUrls: 0,
|
|
67
|
+
metrics: [],
|
|
68
|
+
significantChanges: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const metrics = [
|
|
72
|
+
changedField(records, 'Current Page Title', 'Previous Page Title', 'Page titles', sampleLimit),
|
|
73
|
+
changedField(records, 'Current Meta Description', 'Previous Meta Description', 'Meta descriptions', sampleLimit),
|
|
74
|
+
changedField(records, 'Current H1', 'Previous H1', 'H1 headings', sampleLimit),
|
|
75
|
+
changedField(records, 'Current Indexability', 'Previous Indexability', 'Indexability status', sampleLimit),
|
|
76
|
+
changedNumericField(records, 'Current Word Count', 'Previous Word Count', 'Word count (≥50 words or ≥10%)', 50, sampleLimit),
|
|
77
|
+
];
|
|
78
|
+
const significantChanges = metrics.reduce((sum, metric) => sum + metric.changed, 0);
|
|
79
|
+
return {
|
|
80
|
+
matchedUrls,
|
|
81
|
+
metrics: metrics.filter((metric) => metric.changed > 0),
|
|
82
|
+
significantChanges,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildScreamingFrogCompareMarkdownReport(result, analysis) {
|
|
86
|
+
const lines = [
|
|
87
|
+
`# ${result.siteLabel} Screaming Frog crawl comparison`,
|
|
88
|
+
'',
|
|
89
|
+
`**Generated:** ${new Date().toISOString()}`,
|
|
90
|
+
`**Current crawl:** \`${result.currentDatabaseId}\`${result.currentCrawl ? ` (${result.currentCrawl.modified}, ${result.currentCrawl.urlCount} URLs)` : ''}`,
|
|
91
|
+
`**Previous crawl:** \`${result.previousDatabaseId}\`${result.previousCrawl ? ` (${result.previousCrawl.modified}, ${result.previousCrawl.urlCount} URLs)` : ''}`,
|
|
92
|
+
`**Export:** \`${path.basename(result.exportDir)}\``,
|
|
93
|
+
'',
|
|
94
|
+
'Uses Screaming Frog native **Compare** mode (`--crawl-comparison`) with Change Detection exports.',
|
|
95
|
+
'',
|
|
96
|
+
'## Summary',
|
|
97
|
+
'',
|
|
98
|
+
'| Metric | Count |',
|
|
99
|
+
'|--------|------:|',
|
|
100
|
+
`| URLs matched in both crawls | ${analysis.matchedUrls} |`,
|
|
101
|
+
`| Total field changes (sum of categories) | ${analysis.significantChanges} |`,
|
|
102
|
+
'',
|
|
103
|
+
];
|
|
104
|
+
if (analysis.matchedUrls === 0 && analysis.significantChanges === 0) {
|
|
105
|
+
lines.push('No URLs matched between the two crawls. Compare only works when both crawls cover the same site/profile.', '', 'Typical causes:', '', '- Different preview URLs or profiles', '- Comparing crawls from different hostnames without URL mapping', '- One crawl incomplete or against a different deployment', '');
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
if (analysis.metrics.length === 0 && analysis.matchedUrls > 0) {
|
|
109
|
+
lines.push('No significant content/metadata changes detected in Change Detection exports.', '');
|
|
110
|
+
}
|
|
111
|
+
for (const metric of analysis.metrics) {
|
|
112
|
+
lines.push(`## ${metric.label} (${metric.changed})`, '');
|
|
113
|
+
if (metric.samples.length === 0) {
|
|
114
|
+
lines.push('_No samples exported._', '');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const sample of metric.samples) {
|
|
118
|
+
lines.push(`- ${sample.url}`);
|
|
119
|
+
lines.push(` - before: ${sample.before}`);
|
|
120
|
+
lines.push(` - after: ${sample.after}`);
|
|
121
|
+
}
|
|
122
|
+
if (metric.changed > metric.samples.length) {
|
|
123
|
+
lines.push(`- _…and ${metric.changed - metric.samples.length} more in export_`);
|
|
124
|
+
}
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
127
|
+
lines.push('## Interpretation', '', '- **Content-push baseline → post-code crawl:** title/meta/H1/word-count changes may indicate unintended template or CMS regressions.', '- **Indexability changes** on preview are often noise (sitewide noindex); focus on production/localhost profiles for indexing diffs.', '- Re-run with explicit database IDs if auto-selected crawls are wrong: `site-check-screaming-frog compare <current-id> <previous-id>`.', '');
|
|
128
|
+
return lines.join('\n');
|
|
129
|
+
}
|
|
130
|
+
export async function writeScreamingFrogCompareOutputs(result, analysis, outputDir, outputMdName) {
|
|
131
|
+
const { mkdir, writeFile } = await import('node:fs/promises');
|
|
132
|
+
await mkdir(outputDir, { recursive: true });
|
|
133
|
+
const mdPath = path.join(outputDir, outputMdName);
|
|
134
|
+
await writeFile(mdPath, buildScreamingFrogCompareMarkdownReport(result, analysis), 'utf8');
|
|
135
|
+
return { mdPath };
|
|
136
|
+
}
|
|
137
|
+
export function getScreamingFrogCompareExitCode(analysis) {
|
|
138
|
+
const contentMetrics = analysis.metrics.filter((metric) => ['Page titles', 'Meta descriptions', 'H1 headings', 'Word count (≥50 words or ≥10%)'].includes(metric.label));
|
|
139
|
+
const regressions = contentMetrics.reduce((sum, metric) => sum + metric.changed, 0);
|
|
140
|
+
return regressions > 0 ? 1 : 0;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=compare-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-report.js","sourceRoot":"","sources":["../../src/screaming-frog/compare-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ScreamingFrogCompareCliOptions, ScreamingFrogCompareResult } from './types.js';
|
|
2
|
+
/** Native SF compare exports (Change Detection + response codes). */
|
|
3
|
+
export declare const DEFAULT_COMPARE_EXPORT_TABS: readonly ["Change Detection:All", "Change Detection:Page Titles", "Change Detection:Meta Description", "Change Detection:H1", "Change Detection:Word Count", "Change Detection:Indexability", "Change Detection:Structured Data Unique Types", "Response Codes:All"];
|
|
4
|
+
export declare function runScreamingFrogCompare(options: ScreamingFrogCompareCliOptions): Promise<ScreamingFrogCompareResult>;
|
|
5
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/screaming-frog/compare.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,8BAA8B,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7F,qEAAqE;AACrE,eAAO,MAAM,2BAA2B,sQAS9B,CAAC;AAEX,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAiCrC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { defaultExportRoot, timestampExportDir } from './paths.js';
|
|
3
|
+
import { runScreamingFrogOrThrow } from './sf-runner.js';
|
|
4
|
+
/** Native SF compare exports (Change Detection + response codes). */
|
|
5
|
+
export const DEFAULT_COMPARE_EXPORT_TABS = [
|
|
6
|
+
'Change Detection:All',
|
|
7
|
+
'Change Detection:Page Titles',
|
|
8
|
+
'Change Detection:Meta Description',
|
|
9
|
+
'Change Detection:H1',
|
|
10
|
+
'Change Detection:Word Count',
|
|
11
|
+
'Change Detection:Indexability',
|
|
12
|
+
'Change Detection:Structured Data Unique Types',
|
|
13
|
+
'Response Codes:All',
|
|
14
|
+
];
|
|
15
|
+
export async function runScreamingFrogCompare(options) {
|
|
16
|
+
const exportRoot = options.exportRoot ?? defaultExportRoot();
|
|
17
|
+
const exportDir = options.exportDir ?? timestampExportDir(exportRoot);
|
|
18
|
+
await mkdir(exportDir, { recursive: true });
|
|
19
|
+
const tabs = options.exportTabs ?? DEFAULT_COMPARE_EXPORT_TABS;
|
|
20
|
+
await runScreamingFrogOrThrow([
|
|
21
|
+
'--headless',
|
|
22
|
+
'--crawl-comparison',
|
|
23
|
+
options.currentDatabaseId,
|
|
24
|
+
options.previousDatabaseId,
|
|
25
|
+
'--output-folder',
|
|
26
|
+
exportDir,
|
|
27
|
+
'--overwrite',
|
|
28
|
+
'--export-format',
|
|
29
|
+
'csv',
|
|
30
|
+
'--export-tabs',
|
|
31
|
+
tabs.join(','),
|
|
32
|
+
], { inherit: true });
|
|
33
|
+
return {
|
|
34
|
+
exportDir,
|
|
35
|
+
currentDatabaseId: options.currentDatabaseId,
|
|
36
|
+
previousDatabaseId: options.previousDatabaseId,
|
|
37
|
+
siteLabel: options.siteLabel,
|
|
38
|
+
currentCrawl: options.currentCrawl,
|
|
39
|
+
previousCrawl: options.previousCrawl,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/screaming-frog/compare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,qEAAqE;AACrE,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,sBAAsB;IACtB,8BAA8B;IAC9B,mCAAmC;IACnC,qBAAqB;IACrB,6BAA6B;IAC7B,+BAA+B;IAC/B,+CAA+C;IAC/C,oBAAoB;CACZ,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAuC;IAEvC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,EAAE,CAAC;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAEtE,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5C,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,IAAI,2BAA2B,CAAC;IAE/D,MAAM,uBAAuB,CAC3B;QACE,YAAY;QACZ,oBAAoB;QACpB,OAAO,CAAC,iBAAiB;QACzB,OAAO,CAAC,kBAAkB;QAC1B,iBAAiB;QACjB,SAAS;QACT,aAAa;QACb,iBAAiB;QACjB,KAAK;QACL,eAAe;QACf,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;KACf,EACD,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAC;IAEF,OAAO;QACL,SAAS;QACT,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ScreamingFrogCrawlSnapshot {
|
|
2
|
+
databaseId: string;
|
|
3
|
+
exportDir?: string;
|
|
4
|
+
profile?: string;
|
|
5
|
+
crawlUrl?: string;
|
|
6
|
+
savedAt: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ScreamingFrogCrawlState {
|
|
10
|
+
baselines: Record<string, ScreamingFrogCrawlSnapshot>;
|
|
11
|
+
lastCrawl?: ScreamingFrogCrawlSnapshot;
|
|
12
|
+
}
|
|
13
|
+
export declare function crawlStatePath(workspaceRoot: string): string;
|
|
14
|
+
export declare function loadCrawlState(workspaceRoot: string): Promise<ScreamingFrogCrawlState>;
|
|
15
|
+
export declare function saveCrawlState(workspaceRoot: string, state: ScreamingFrogCrawlState): Promise<string>;
|
|
16
|
+
export declare function recordCrawlSnapshot(workspaceRoot: string, snapshot: ScreamingFrogCrawlSnapshot, options?: {
|
|
17
|
+
tag?: string;
|
|
18
|
+
}): Promise<string>;
|
|
19
|
+
//# sourceMappingURL=crawl-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl-state.d.ts","sourceRoot":"","sources":["../../src/screaming-frog/crawl-state.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;IACtD,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACxC;AAID,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAO5F;AAED,wBAAsB,cAAc,CAClC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,0BAA0B,EACpC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC,MAAM,CAAC,CAOjB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const STATE_FILE = 'docs/seo/sf-crawl-state.json';
|
|
4
|
+
export function crawlStatePath(workspaceRoot) {
|
|
5
|
+
return path.join(workspaceRoot, STATE_FILE);
|
|
6
|
+
}
|
|
7
|
+
export async function loadCrawlState(workspaceRoot) {
|
|
8
|
+
try {
|
|
9
|
+
const raw = await readFile(crawlStatePath(workspaceRoot), 'utf8');
|
|
10
|
+
return JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { baselines: {} };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function saveCrawlState(workspaceRoot, state) {
|
|
17
|
+
const filePath = crawlStatePath(workspaceRoot);
|
|
18
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
19
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
20
|
+
return filePath;
|
|
21
|
+
}
|
|
22
|
+
export async function recordCrawlSnapshot(workspaceRoot, snapshot, options = {}) {
|
|
23
|
+
const state = await loadCrawlState(workspaceRoot);
|
|
24
|
+
state.lastCrawl = snapshot;
|
|
25
|
+
if (options.tag) {
|
|
26
|
+
state.baselines[options.tag] = snapshot;
|
|
27
|
+
}
|
|
28
|
+
return saveCrawlState(workspaceRoot, state);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=crawl-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl-state.js","sourceRoot":"","sources":["../../src/screaming-frog/crawl-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAgB7B,MAAM,UAAU,GAAG,8BAA8B,CAAC;AAElD,MAAM,UAAU,cAAc,CAAC,aAAqB;IAClD,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,aAAqB;IACxD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,aAAqB,EACrB,KAA8B;IAE9B,MAAM,QAAQ,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,aAAqB,EACrB,QAAoC,EACpC,UAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;IAClD,KAAK,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC3B,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IAC1C,CAAC;IACD,OAAO,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ScreamingFrogCrawlCliOptions, ScreamingFrogCrawlResult } from './types.js';
|
|
2
|
+
/** CSV tabs exported after every crawl for audit analysis. */
|
|
3
|
+
export declare const DEFAULT_EXPORT_TABS: readonly ["Internal:All", "Response Codes:All", "Canonicals:All", "Directives:All", "Structured Data:All", "H1:All", "Page Titles:All", "Meta Description:All", "All Inlinks"];
|
|
4
|
+
export declare const DEFAULT_EXPORT_REPORTS: readonly ["Crawl Overview", "Issues Overview"];
|
|
5
|
+
export declare function runScreamingFrogCrawl(options: ScreamingFrogCrawlCliOptions): Promise<ScreamingFrogCrawlResult>;
|
|
6
|
+
//# sourceMappingURL=crawl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/screaming-frog/crawl.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAEzF,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,gLAUtB,CAAC;AAEX,eAAO,MAAM,sBAAsB,gDAAiD,CAAC;AAiCrF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,wBAAwB,CAAC,CAuCnC"}
|