@eightmedia/flightcheck 0.0.0-stage → 1.0.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +691 -0
  3. package/README.md +152 -2
  4. package/dist/analyze/fixManifest.d.ts +65 -0
  5. package/dist/analyze/fixManifest.d.ts.map +1 -0
  6. package/dist/analyze/fixManifest.js +588 -0
  7. package/dist/analyze/report.d.ts +283 -0
  8. package/dist/analyze/report.d.ts.map +1 -0
  9. package/dist/analyze/report.js +726 -0
  10. package/dist/analyze/reportTier1.d.ts +46 -0
  11. package/dist/analyze/reportTier1.d.ts.map +1 -0
  12. package/dist/analyze/reportTier1.js +251 -0
  13. package/dist/analyze/stack.d.ts +33 -0
  14. package/dist/analyze/stack.d.ts.map +1 -0
  15. package/dist/analyze/stack.js +188 -0
  16. package/dist/analyze/structuredReport.d.ts +62 -0
  17. package/dist/analyze/structuredReport.d.ts.map +1 -0
  18. package/dist/analyze/structuredReport.js +524 -0
  19. package/dist/analyze.d.ts +3 -0
  20. package/dist/analyze.d.ts.map +1 -0
  21. package/dist/analyze.js +40 -0
  22. package/dist/checks/agent-ready.d.ts +63 -0
  23. package/dist/checks/agent-ready.d.ts.map +1 -0
  24. package/dist/checks/agent-ready.js +219 -0
  25. package/dist/checks/analyze.d.ts +15 -0
  26. package/dist/checks/analyze.d.ts.map +1 -0
  27. package/dist/checks/analyze.js +63 -0
  28. package/dist/checks/crawl.d.ts +28 -0
  29. package/dist/checks/crawl.d.ts.map +1 -0
  30. package/dist/checks/crawl.js +86 -0
  31. package/dist/checks/dns.d.ts +25 -0
  32. package/dist/checks/dns.d.ts.map +1 -0
  33. package/dist/checks/dns.js +80 -0
  34. package/dist/checks/index.d.ts +24 -0
  35. package/dist/checks/index.d.ts.map +1 -0
  36. package/dist/checks/index.js +12 -0
  37. package/dist/checks/issues.d.ts +8 -0
  38. package/dist/checks/issues.d.ts.map +1 -0
  39. package/dist/checks/issues.js +3 -0
  40. package/dist/checks/redirect.d.ts +38 -0
  41. package/dist/checks/redirect.d.ts.map +1 -0
  42. package/dist/checks/redirect.js +209 -0
  43. package/dist/checks/registry.d.ts +8 -0
  44. package/dist/checks/registry.d.ts.map +1 -0
  45. package/dist/checks/registry.js +275 -0
  46. package/dist/checks/robots-txt.d.ts +16 -0
  47. package/dist/checks/robots-txt.d.ts.map +1 -0
  48. package/dist/checks/robots-txt.js +99 -0
  49. package/dist/checks/runner.d.ts +9 -0
  50. package/dist/checks/runner.d.ts.map +1 -0
  51. package/dist/checks/runner.js +110 -0
  52. package/dist/checks/security-headers.d.ts +12 -0
  53. package/dist/checks/security-headers.d.ts.map +1 -0
  54. package/dist/checks/security-headers.js +53 -0
  55. package/dist/checks/sitemap.d.ts +36 -0
  56. package/dist/checks/sitemap.d.ts.map +1 -0
  57. package/dist/checks/sitemap.js +140 -0
  58. package/dist/checks/types.d.ts +72 -0
  59. package/dist/checks/types.d.ts.map +1 -0
  60. package/dist/checks/types.js +20 -0
  61. package/dist/cli.d.ts +3 -0
  62. package/dist/cli.d.ts.map +1 -0
  63. package/dist/cli.js +125 -0
  64. package/dist/crawler/checkGroups.d.ts +7 -0
  65. package/dist/crawler/checkGroups.d.ts.map +1 -0
  66. package/dist/crawler/checkGroups.js +29 -0
  67. package/dist/crawler/crawlState.d.ts +7 -0
  68. package/dist/crawler/crawlState.d.ts.map +1 -0
  69. package/dist/crawler/crawlState.js +6 -0
  70. package/dist/crawler/defaults.d.ts +11 -0
  71. package/dist/crawler/defaults.d.ts.map +1 -0
  72. package/dist/crawler/defaults.js +10 -0
  73. package/dist/crawler/extractors.d.ts +5 -0
  74. package/dist/crawler/extractors.d.ts.map +1 -0
  75. package/dist/crawler/extractors.js +169 -0
  76. package/dist/crawler/jsAssets.d.ts +2 -0
  77. package/dist/crawler/jsAssets.d.ts.map +1 -0
  78. package/dist/crawler/jsAssets.js +22 -0
  79. package/dist/crawler/outputPaths.d.ts +14 -0
  80. package/dist/crawler/outputPaths.d.ts.map +1 -0
  81. package/dist/crawler/outputPaths.js +51 -0
  82. package/dist/crawler/reporter.d.ts +3 -0
  83. package/dist/crawler/reporter.d.ts.map +1 -0
  84. package/dist/crawler/reporter.js +12 -0
  85. package/dist/crawler/robots.d.ts +5 -0
  86. package/dist/crawler/robots.d.ts.map +1 -0
  87. package/dist/crawler/robots.js +29 -0
  88. package/dist/crawler/runCrawler.d.ts +22 -0
  89. package/dist/crawler/runCrawler.d.ts.map +1 -0
  90. package/dist/crawler/runCrawler.js +172 -0
  91. package/dist/crawler/schema.d.ts +87 -0
  92. package/dist/crawler/schema.d.ts.map +1 -0
  93. package/dist/crawler/schema.js +44 -0
  94. package/dist/crawler/secrets.d.ts +4 -0
  95. package/dist/crawler/secrets.d.ts.map +1 -0
  96. package/dist/crawler/secrets.js +70 -0
  97. package/dist/crawler/sitemap.d.ts +8 -0
  98. package/dist/crawler/sitemap.d.ts.map +1 -0
  99. package/dist/crawler/sitemap.js +105 -0
  100. package/dist/crawler/stack.d.ts +3 -0
  101. package/dist/crawler/stack.d.ts.map +1 -0
  102. package/dist/crawler/stack.js +1 -0
  103. package/dist/crawler/stackDetect.d.ts +24 -0
  104. package/dist/crawler/stackDetect.d.ts.map +1 -0
  105. package/dist/crawler/stackDetect.js +604 -0
  106. package/dist/crawler/tier1Extractors.d.ts +5 -0
  107. package/dist/crawler/tier1Extractors.d.ts.map +1 -0
  108. package/dist/crawler/tier1Extractors.js +255 -0
  109. package/dist/crawler/tracking.d.ts +8 -0
  110. package/dist/crawler/tracking.d.ts.map +1 -0
  111. package/dist/crawler/tracking.js +50 -0
  112. package/dist/crawler/urlUtils.d.ts +12 -0
  113. package/dist/crawler/urlUtils.d.ts.map +1 -0
  114. package/dist/crawler/urlUtils.js +53 -0
  115. package/dist/index.d.ts +14 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +7 -0
  118. package/docs/agent-fixes.md +17 -0
  119. package/docs/agent-ready.md +44 -0
  120. package/docs/analyze.md +103 -0
  121. package/docs/check-api.md +142 -0
  122. package/docs/checks.md +116 -0
  123. package/docs/crawler.md +158 -0
  124. package/docs/index.md +24 -0
  125. package/docs/qc-checks-plan.md +796 -0
  126. package/docs/stack.md +99 -0
  127. package/package.json +86 -4
@@ -0,0 +1,219 @@
1
+ import { issue } from "./issues.js";
2
+ import { DEFAULT_USER_AGENT } from "../crawler/defaults.js";
3
+ /** AI crawler user-agents commonly expected in robots.txt (isitagentready / IETF drafts). */
4
+ export const AI_BOT_USER_AGENTS = [
5
+ "GPTBot",
6
+ "OAI-SearchBot",
7
+ "ChatGPT-User",
8
+ "ClaudeBot",
9
+ "Claude-Web",
10
+ "anthropic-ai",
11
+ "Google-Extended",
12
+ "Amazonbot",
13
+ "Applebot-Extended",
14
+ "Bytespider",
15
+ "CCBot",
16
+ "PerplexityBot",
17
+ ];
18
+ /** Link relation types useful for agent discovery (RFC 8288 / RFC 9727). */
19
+ export const AGENT_LINK_RELS = ["api-catalog", "service-desc", "service-doc", "describedby"];
20
+ function isSuccessfulStatus(status) {
21
+ return typeof status === "number" && status >= 200 && status < 300;
22
+ }
23
+ function looksLikeHtml(body, contentType) {
24
+ const type = (contentType ?? "").toLowerCase();
25
+ if (type.includes("text/html") || type.includes("application/xhtml"))
26
+ return true;
27
+ if (!body)
28
+ return false;
29
+ const trimmed = body.trimStart().slice(0, 200).toLowerCase();
30
+ return trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html");
31
+ }
32
+ function extractContentSignals(body) {
33
+ const signals = [];
34
+ for (const line of body.split("\n")) {
35
+ const match = line.match(/^\s*content-signal:\s*(.+)\s*$/i);
36
+ if (match)
37
+ signals.push(match[1].trim());
38
+ }
39
+ return signals;
40
+ }
41
+ function extractAiBotUserAgents(body) {
42
+ const found = new Set();
43
+ for (const bot of AI_BOT_USER_AGENTS) {
44
+ const pattern = new RegExp(`^\\s*user-agent:\\s*${bot}\\s*$`, "im");
45
+ if (pattern.test(body))
46
+ found.add(bot);
47
+ }
48
+ return [...found].sort((a, b) => a.localeCompare(b));
49
+ }
50
+ function parseLinkRelations(linkHeader) {
51
+ if (!linkHeader)
52
+ return [];
53
+ const relations = new Set();
54
+ for (const part of linkHeader.split(/,(?=\s*<)/)) {
55
+ const relMatch = part.match(/\brel\s*=\s*"([^"]+)"/i) ?? part.match(/\brel\s*=\s*'([^']+)'/i) ?? part.match(/\brel\s*=\s*([^\s;]+)/i);
56
+ if (!relMatch)
57
+ continue;
58
+ for (const rel of relMatch[1].split(/\s+/)) {
59
+ const normalized = rel.trim().toLowerCase();
60
+ if (normalized)
61
+ relations.add(normalized);
62
+ }
63
+ }
64
+ return [...relations].sort((a, b) => a.localeCompare(b));
65
+ }
66
+ function isPresentJsonOrDocument(probe) {
67
+ if (!probe || !isSuccessfulStatus(probe.status))
68
+ return false;
69
+ if (looksLikeHtml(probe.body, probe.contentType))
70
+ return false;
71
+ return true;
72
+ }
73
+ export function analyzeAgentReady(input) {
74
+ const issues = [];
75
+ const robotsBody = input.robotsTxt?.body ?? "";
76
+ const contentSignals = robotsBody ? extractContentSignals(robotsBody) : [];
77
+ const aiBotUserAgents = robotsBody ? extractAiBotUserAgents(robotsBody) : [];
78
+ const llmsStatus = input.llmsTxt?.status ?? null;
79
+ const llmsBody = input.llmsTxt?.body ?? "";
80
+ const llmsIsHtml = looksLikeHtml(llmsBody, input.llmsTxt?.contentType);
81
+ const llmsOk = isSuccessfulStatus(llmsStatus) && !llmsIsHtml && llmsBody.trim().length > 0;
82
+ const llmsHasTitle = /^#\s+\S+/m.test(llmsBody);
83
+ const markdownType = input.markdown?.contentType ?? "";
84
+ const markdownOk = isSuccessfulStatus(input.markdown?.status ?? null) &&
85
+ /text\/markdown|text\/x-markdown/i.test(markdownType) &&
86
+ !looksLikeHtml(input.markdown?.body, markdownType);
87
+ const linkHeader = input.homepage?.headers?.link ?? input.homepage?.headers?.Link;
88
+ const linkRelations = parseLinkRelations(linkHeader);
89
+ const agentLinkRelations = linkRelations.filter((rel) => AGENT_LINK_RELS.includes(rel));
90
+ const wellKnown = {
91
+ mcpServerCard: isPresentJsonOrDocument(input.wellKnown?.mcpServerCard),
92
+ agentSkills: isPresentJsonOrDocument(input.wellKnown?.agentSkills),
93
+ apiCatalog: isPresentJsonOrDocument(input.wellKnown?.apiCatalog),
94
+ ard: isPresentJsonOrDocument(input.wellKnown?.ard),
95
+ a2aAgentCard: isPresentJsonOrDocument(input.wellKnown?.a2aAgentCard),
96
+ };
97
+ const robotsStatus = input.robotsTxt?.status ?? null;
98
+ if (!isSuccessfulStatus(robotsStatus) || !robotsBody.trim()) {
99
+ issues.push(issue("agent-robots-missing", "warning", "robots.txt is missing or empty; agents cannot discover crawl policy or Content Signals."));
100
+ }
101
+ else {
102
+ if (contentSignals.length === 0) {
103
+ issues.push(issue("agent-missing-content-signals", "warning", "robots.txt has no Content-Signal directives (ai-train / search / ai-input)."));
104
+ }
105
+ if (aiBotUserAgents.length === 0) {
106
+ issues.push(issue("agent-missing-ai-bot-rules", "warning", "robots.txt has no explicit AI bot User-agent rules (e.g. GPTBot, ClaudeBot, Google-Extended)."));
107
+ }
108
+ }
109
+ if (!llmsOk) {
110
+ issues.push(issue("agent-missing-llms-txt", "warning", "llms.txt is missing or not a plain-text overview at /llms.txt."));
111
+ }
112
+ else if (!llmsHasTitle) {
113
+ issues.push(issue("agent-llms-txt-incomplete", "review", "llms.txt exists but has no H1 title line (# Site Name)."));
114
+ }
115
+ if (!markdownOk) {
116
+ issues.push(issue("agent-missing-markdown-negotiation", "warning", "Homepage does not negotiate Markdown (Accept: text/markdown → Content-Type: text/markdown)."));
117
+ }
118
+ if (agentLinkRelations.length === 0) {
119
+ issues.push(issue("agent-missing-link-headers", "review", 'Homepage has no agent-useful Link headers (rel="api-catalog", "service-doc", "service-desc", or "describedby").'));
120
+ }
121
+ const wellKnownPresent = Object.values(wellKnown).some(Boolean);
122
+ if (!wellKnownPresent) {
123
+ issues.push(issue("agent-missing-protocol-discovery", "review", "No agent protocol discovery endpoints found (MCP Server Card, Agent Skills, API Catalog, ARD, or A2A Agent Card)."));
124
+ }
125
+ const findings = {
126
+ robotsTxt: {
127
+ status: robotsStatus,
128
+ hasContentSignals: contentSignals.length > 0,
129
+ contentSignals,
130
+ aiBotUserAgents,
131
+ },
132
+ llmsTxt: {
133
+ url: input.llmsTxt?.url ?? `${input.origin}/llms.txt`,
134
+ status: llmsStatus,
135
+ hasTitle: llmsHasTitle,
136
+ },
137
+ markdownNegotiation: {
138
+ url: input.markdown?.url ?? input.origin,
139
+ status: input.markdown?.status ?? null,
140
+ contentType: input.markdown?.contentType,
141
+ supportsMarkdown: markdownOk,
142
+ },
143
+ linkHeaders: {
144
+ relations: agentLinkRelations,
145
+ },
146
+ wellKnown,
147
+ };
148
+ return {
149
+ url: input.origin,
150
+ findings,
151
+ issues,
152
+ };
153
+ }
154
+ async function fetchProbe(url, init = {}, timeoutMs = 10_000) {
155
+ const controller = new AbortController();
156
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
157
+ try {
158
+ const response = await fetch(url, {
159
+ redirect: "follow",
160
+ signal: controller.signal,
161
+ ...init,
162
+ headers: {
163
+ "user-agent": DEFAULT_USER_AGENT,
164
+ ...(init.headers ?? {}),
165
+ },
166
+ });
167
+ const headers = {};
168
+ response.headers.forEach((value, key) => {
169
+ headers[key.toLowerCase()] = value;
170
+ });
171
+ const contentType = headers["content-type"];
172
+ const body = await response.text();
173
+ return {
174
+ url,
175
+ status: response.status,
176
+ contentType,
177
+ body: body.slice(0, 100_000),
178
+ headers,
179
+ };
180
+ }
181
+ catch {
182
+ return { url, status: null };
183
+ }
184
+ finally {
185
+ clearTimeout(timeout);
186
+ }
187
+ }
188
+ export async function runAgentReadyCheck(startUrl, timeoutMs = 10_000) {
189
+ const origin = new URL(startUrl).origin;
190
+ const [robotsTxt, llmsTxt, markdown, homepage, mcpServerCard, agentSkills, apiCatalog, ard, a2aAgentCard,] = await Promise.all([
191
+ fetchProbe(`${origin}/robots.txt`, {}, timeoutMs),
192
+ fetchProbe(`${origin}/llms.txt`, {}, timeoutMs),
193
+ fetchProbe(origin, {
194
+ headers: {
195
+ accept: "text/markdown, text/x-markdown;q=0.9, text/plain;q=0.8, */*;q=0.1",
196
+ },
197
+ }, timeoutMs),
198
+ fetchProbe(origin, {}, timeoutMs),
199
+ fetchProbe(`${origin}/.well-known/mcp/server-card.json`, {}, timeoutMs),
200
+ fetchProbe(`${origin}/.well-known/agent-skills/index.json`, {}, timeoutMs),
201
+ fetchProbe(`${origin}/.well-known/api-catalog`, {}, timeoutMs),
202
+ fetchProbe(`${origin}/.well-known/ai-catalog.json`, {}, timeoutMs),
203
+ fetchProbe(`${origin}/.well-known/agent-card.json`, {}, timeoutMs),
204
+ ]);
205
+ return analyzeAgentReady({
206
+ origin,
207
+ robotsTxt,
208
+ llmsTxt,
209
+ markdown,
210
+ homepage,
211
+ wellKnown: {
212
+ mcpServerCard,
213
+ agentSkills,
214
+ apiCatalog,
215
+ ard,
216
+ a2aAgentCard,
217
+ },
218
+ });
219
+ }
@@ -0,0 +1,15 @@
1
+ import { type ReportRecord, type Tier1ReportData } from "../analyze/report.js";
2
+ import type { AnalyzeCheckResult } from "./types.js";
3
+ export type AnalyzeCheckOptions = {
4
+ records?: ReportRecord[];
5
+ inputPath?: string;
6
+ reportPath?: string;
7
+ reportJsonPath?: string;
8
+ siteUrl?: string;
9
+ errorsOnly?: boolean;
10
+ writeReport?: boolean;
11
+ redirectOnly?: boolean;
12
+ tier1?: Tier1ReportData;
13
+ };
14
+ export declare function runAnalyzeCheck(options: AnalyzeCheckOptions): Promise<AnalyzeCheckResult>;
15
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/checks/analyze.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAuBF,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4C/F"}
@@ -0,0 +1,63 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { parseReport } from "../analyze/report.js";
4
+ import { buildStackProfile } from "../analyze/stack.js";
5
+ import { buildStructuredReport, renderReportMarkdown, } from "../analyze/structuredReport.js";
6
+ import { defaultReportJsonPathForInput, defaultReportPathForInput, } from "../crawler/outputPaths.js";
7
+ function loadRecords(options) {
8
+ if (options.records !== undefined) {
9
+ return {
10
+ records: options.records,
11
+ inputPath: options.inputPath ?? "in-memory-crawl.json",
12
+ };
13
+ }
14
+ if (!options.inputPath) {
15
+ throw new Error("Analyze check requires crawl records or an inputPath to an existing JSON report.");
16
+ }
17
+ const inputPath = resolve(options.inputPath);
18
+ const raw = readFileSync(inputPath, "utf8");
19
+ const json = JSON.parse(raw);
20
+ return {
21
+ records: parseReport(json),
22
+ inputPath,
23
+ };
24
+ }
25
+ export async function runAnalyzeCheck(options) {
26
+ const { records, inputPath } = loadRecords(options);
27
+ const reportPath = resolve(options.reportPath ?? defaultReportPathForInput(inputPath));
28
+ const reportJsonPath = resolve(options.reportJsonPath ?? defaultReportJsonPathForInput(inputPath));
29
+ const redirectOnly = options.redirectOnly ?? false;
30
+ const stackProfile = redirectOnly
31
+ ? { detections: [], tracking: { tagManager: [], analytics: [] } }
32
+ : buildStackProfile(records, {
33
+ securityHeaders: options.tier1?.securityHeaders,
34
+ dns: options.tier1?.dns,
35
+ robotsTxt: options.tier1?.robotsTxt,
36
+ });
37
+ const report = await buildStructuredReport(inputPath, records, options.tier1, {
38
+ redirectOnly,
39
+ stackProfile,
40
+ });
41
+ // errorsOnly currently filters only the Markdown presentation of page-level noise;
42
+ // the structured JSON keeps the full finding set for machine consumers.
43
+ const markdown = renderReportMarkdown(options.errorsOnly
44
+ ? {
45
+ ...report,
46
+ findings: report.findings.filter((finding) => finding.severity === "critical" || finding.severity === "error"),
47
+ }
48
+ : report);
49
+ if (options.writeReport !== false) {
50
+ mkdirSync(dirname(reportPath), { recursive: true });
51
+ writeFileSync(reportPath, markdown, "utf8");
52
+ mkdirSync(dirname(reportJsonPath), { recursive: true });
53
+ writeFileSync(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
54
+ }
55
+ return {
56
+ inputPath,
57
+ reportPath,
58
+ reportJsonPath,
59
+ markdown,
60
+ report,
61
+ pagesAnalyzed: records.length,
62
+ };
63
+ }
@@ -0,0 +1,28 @@
1
+ import { type CheckGroup } from "../crawler/checkGroups.js";
2
+ import type { CrawlCheckResult } from "./types.js";
3
+ export type CrawlCheckOptions = {
4
+ startUrl: string;
5
+ outputPath?: string;
6
+ maxPages?: number;
7
+ delayMs?: number;
8
+ timeoutMs?: number;
9
+ concurrency?: number;
10
+ burstDelayMs?: number;
11
+ sitemapStart?: boolean;
12
+ sitemapUrl?: string;
13
+ excludePatterns?: string[];
14
+ respectRobotsTxt?: boolean;
15
+ activeGroups?: Set<CheckGroup>;
16
+ only?: string;
17
+ skip?: string;
18
+ revealSecrets?: boolean;
19
+ quiet?: boolean;
20
+ };
21
+ export declare function prepareCrawlSeedUrls(options: CrawlCheckOptions): Promise<{
22
+ normalizedStartUrl: string;
23
+ seedUrls: string[];
24
+ outputPath: string;
25
+ activeGroups: Set<CheckGroup>;
26
+ }>;
27
+ export declare function runCrawlCheck(options: CrawlCheckOptions): Promise<CrawlCheckResult>;
28
+ //# sourceMappingURL=crawl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/checks/crawl.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAKjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC9E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAC/B,CAAC,CAyDD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiCzF"}
@@ -0,0 +1,86 @@
1
+ import { DEFAULT_BURST_DELAY_MS, DEFAULT_CONCURRENCY, DEFAULT_DELAY_MS, DEFAULT_MAX_PAGES, DEFAULT_TIMEOUT_MS, } from "../crawler/defaults.js";
2
+ import { resolveActiveGroups } from "../crawler/checkGroups.js";
3
+ import { buildDefaultOutputPaths } from "../crawler/outputPaths.js";
4
+ import { runCrawler } from "../crawler/runCrawler.js";
5
+ import { collectSitemapPageUrls } from "./sitemap.js";
6
+ import { isHttpUrl, matchesExcludePattern, normalizeUrl } from "../crawler/urlUtils.js";
7
+ export async function prepareCrawlSeedUrls(options) {
8
+ if (!isHttpUrl(options.startUrl)) {
9
+ throw new Error("<start-url> must be a valid http(s) URL.");
10
+ }
11
+ const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;
12
+ const delayMs = options.delayMs ?? DEFAULT_DELAY_MS;
13
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
14
+ const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
15
+ const burstDelayMs = options.burstDelayMs ?? DEFAULT_BURST_DELAY_MS;
16
+ if (!Number.isFinite(maxPages) || maxPages <= 0)
17
+ throw new Error("--max-pages must be a positive number.");
18
+ if (!Number.isFinite(delayMs) || delayMs < 0)
19
+ throw new Error("--delay-ms must be zero or a positive number.");
20
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
21
+ throw new Error("--timeout-ms must be a positive number.");
22
+ if (!Number.isFinite(concurrency) || concurrency <= 0)
23
+ throw new Error("--concurrency must be a positive number.");
24
+ if (!Number.isFinite(burstDelayMs) || burstDelayMs < 0) {
25
+ throw new Error("--burst-delay-ms must be zero or a positive number.");
26
+ }
27
+ let activeGroups = options.activeGroups;
28
+ if (!activeGroups) {
29
+ try {
30
+ activeGroups = resolveActiveGroups({ only: options.only, skip: options.skip });
31
+ }
32
+ catch (error) {
33
+ throw new Error(error instanceof Error ? error.message : String(error));
34
+ }
35
+ }
36
+ const normalizedStartUrl = normalizeUrl(options.startUrl);
37
+ const defaultPaths = buildDefaultOutputPaths(normalizedStartUrl);
38
+ const outputPath = options.outputPath ?? defaultPaths.rawJson;
39
+ const excludePatterns = options.excludePatterns ?? [];
40
+ const sitemapPageUrls = await collectSitemapPageUrls(normalizedStartUrl, timeoutMs, [], options.sitemapUrl);
41
+ let seedUrls = [normalizedStartUrl];
42
+ for (const url of sitemapPageUrls) {
43
+ if (!seedUrls.includes(url)) {
44
+ seedUrls.push(url);
45
+ }
46
+ }
47
+ if (options.sitemapStart) {
48
+ seedUrls = [...sitemapPageUrls];
49
+ if (!seedUrls.includes(normalizedStartUrl)) {
50
+ seedUrls.unshift(normalizedStartUrl);
51
+ }
52
+ }
53
+ seedUrls = seedUrls.filter((url) => !matchesExcludePattern(url, excludePatterns));
54
+ return { normalizedStartUrl, seedUrls, outputPath, activeGroups };
55
+ }
56
+ export async function runCrawlCheck(options) {
57
+ const { normalizedStartUrl, seedUrls, outputPath, activeGroups } = await prepareCrawlSeedUrls(options);
58
+ const maxPages = options.maxPages ?? DEFAULT_MAX_PAGES;
59
+ const delayMs = options.delayMs ?? DEFAULT_DELAY_MS;
60
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
61
+ const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
62
+ const burstDelayMs = options.burstDelayMs ?? DEFAULT_BURST_DELAY_MS;
63
+ const excludePatterns = options.excludePatterns ?? [];
64
+ const { records, interrupted } = await runCrawler({
65
+ startUrl: normalizedStartUrl,
66
+ outputPath,
67
+ maxPages: Math.floor(maxPages),
68
+ delayMs: Math.floor(delayMs),
69
+ timeoutMs: Math.floor(timeoutMs),
70
+ concurrency: Math.floor(concurrency),
71
+ burstDelayMs: Math.floor(burstDelayMs),
72
+ seedUrls,
73
+ excludePatterns,
74
+ respectRobotsTxt: options.respectRobotsTxt ?? false,
75
+ activeGroups,
76
+ revealSecrets: options.revealSecrets ?? false,
77
+ quiet: options.quiet ?? false,
78
+ });
79
+ const secretsFound = records.reduce((sum, record) => sum + (record.security?.secrets.length ?? 0), 0);
80
+ return {
81
+ records,
82
+ outputPath,
83
+ interrupted,
84
+ secretsFound,
85
+ };
86
+ }
@@ -0,0 +1,25 @@
1
+ import { type CheckIssue } from "./issues.js";
2
+ export type DnsRecords = {
3
+ a: string[];
4
+ aaaa: string[];
5
+ cname: string[];
6
+ mx: Array<{
7
+ exchange: string;
8
+ priority: number;
9
+ }>;
10
+ txt: string[];
11
+ ns: string[];
12
+ };
13
+ export type DnsMailSignals = {
14
+ spf?: string;
15
+ dmarc?: string;
16
+ };
17
+ export type DnsCheckResult = {
18
+ hostname: string;
19
+ records: DnsRecords;
20
+ mail: DnsMailSignals;
21
+ issues: CheckIssue[];
22
+ };
23
+ export declare function analyzeDnsRecords(input: string): Promise<DnsCheckResult>;
24
+ export declare function runDnsCheck(startUrl: string): Promise<DnsCheckResult>;
25
+ //# sourceMappingURL=dns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../src/checks/dns.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,EAAE,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AA2CF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAuC9E;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAE3E"}
@@ -0,0 +1,80 @@
1
+ import * as nodeDns from "node:dns";
2
+ import { isPlatformPreviewHostname } from "../crawler/urlUtils.js";
3
+ import { issue } from "./issues.js";
4
+ function parseHostname(input) {
5
+ try {
6
+ const url = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
7
+ return url.hostname;
8
+ }
9
+ catch {
10
+ return input;
11
+ }
12
+ }
13
+ async function resolveOptional(resolver, fallback) {
14
+ try {
15
+ return await resolver();
16
+ }
17
+ catch {
18
+ return fallback;
19
+ }
20
+ }
21
+ function flattenTxtRecords(records) {
22
+ return records.map((parts) => parts.join(""));
23
+ }
24
+ function findSpf(txtRecords) {
25
+ return txtRecords.find((record) => record.toLowerCase().startsWith("v=spf1"));
26
+ }
27
+ function findDmarc(hostname, txtRecords) {
28
+ const fromRoot = txtRecords.find((record) => record.toUpperCase().includes("V=DMARC1"));
29
+ if (fromRoot)
30
+ return fromRoot;
31
+ return undefined;
32
+ }
33
+ async function resolveDmarc(hostname) {
34
+ try {
35
+ const records = await nodeDns.promises.resolveTxt(`_dmarc.${hostname}`);
36
+ const flat = flattenTxtRecords(records);
37
+ return flat.find((record) => record.toUpperCase().includes("V=DMARC1"));
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ export async function analyzeDnsRecords(input) {
44
+ const hostname = parseHostname(input);
45
+ const issues = [];
46
+ const [a, aaaa, cname, mx, txt, ns] = await Promise.all([
47
+ resolveOptional(() => nodeDns.promises.resolve4(hostname), []),
48
+ resolveOptional(() => nodeDns.promises.resolve6(hostname), []),
49
+ resolveOptional(() => nodeDns.promises.resolveCname(hostname), []),
50
+ resolveOptional(() => nodeDns.promises.resolveMx(hostname), []),
51
+ resolveOptional(() => nodeDns.promises.resolveTxt(hostname), []),
52
+ resolveOptional(() => nodeDns.promises.resolveNs(hostname), []),
53
+ ]);
54
+ const flatTxt = flattenTxtRecords(txt);
55
+ const spf = findSpf(flatTxt);
56
+ const dmarc = (await resolveDmarc(hostname)) ?? findDmarc(hostname, flatTxt);
57
+ const skipMailAuthIssues = isPlatformPreviewHostname(hostname);
58
+ if (!skipMailAuthIssues && !spf) {
59
+ issues.push(issue("missing-spf", "warning", "No SPF TXT record found."));
60
+ }
61
+ if (!skipMailAuthIssues && !dmarc) {
62
+ issues.push(issue("missing-dmarc", "warning", "No DMARC TXT record found."));
63
+ }
64
+ return {
65
+ hostname,
66
+ records: {
67
+ a,
68
+ aaaa,
69
+ cname,
70
+ mx,
71
+ txt: flatTxt,
72
+ ns,
73
+ },
74
+ mail: { spf, dmarc },
75
+ issues,
76
+ };
77
+ }
78
+ export async function runDnsCheck(startUrl) {
79
+ return analyzeDnsRecords(startUrl);
80
+ }
@@ -0,0 +1,24 @@
1
+ import { CHECK_IDS, DEFAULT_CHECK_IDS } from "./types.js";
2
+ import { CHECK_DEFINITIONS, getCheckDefinition, listChecks, listImplementedChecks } from "./registry.js";
3
+ import { planCheckWaves, runCheck, runChecks } from "./runner.js";
4
+ import { runAnalyzeCheck } from "./analyze.js";
5
+ import { runCrawlCheck, prepareCrawlSeedUrls } from "./crawl.js";
6
+ import { analyzeAgentReady, runAgentReadyCheck } from "./agent-ready.js";
7
+ import { analyzeDnsRecords, runDnsCheck } from "./dns.js";
8
+ import { analyzeRobotsTxt, runRobotsTxtCheck } from "./robots-txt.js";
9
+ import { analyzeSecurityHeaders, runSecurityHeadersCheck } from "./security-headers.js";
10
+ import { analyzeSitemapCompare, runSitemapCheck } from "./sitemap.js";
11
+ import { analyzeRedirect, analyzeWwwAliasRedirect, buildAlternateHostUrl, resolveCanonicalUrl, resolveChecksForRedirect, runRedirectCheck, shouldCheckWwwAlias, toggleWwwHostname } from "./redirect.js";
12
+ export { CHECK_IDS, CHECK_DEFINITIONS, DEFAULT_CHECK_IDS, getCheckDefinition, listChecks, listImplementedChecks, planCheckWaves, runAnalyzeCheck, runCheck, runChecks, runCrawlCheck, prepareCrawlSeedUrls, analyzeAgentReady, runAgentReadyCheck, analyzeDnsRecords, runDnsCheck, analyzeRobotsTxt, runRobotsTxtCheck, analyzeSecurityHeaders, runSecurityHeadersCheck, analyzeSitemapCompare, runSitemapCheck, analyzeRedirect, analyzeWwwAliasRedirect, buildAlternateHostUrl, resolveCanonicalUrl, resolveChecksForRedirect, runRedirectCheck, shouldCheckWwwAlias, toggleWwwHostname, };
13
+ export type { AnalyzeCheckResult, CheckDefinition, CheckExecutionResult, CheckId, CheckOutputs, CheckRunContext, CheckStatus, CrawlCheckResult, ImplementedCheckId, PlannedCheckId, RunChecksOptions, RunChecksResult, } from "./types.js";
14
+ export type { AnalyzeCheckOptions } from "./analyze.js";
15
+ export type { CrawlCheckOptions } from "./crawl.js";
16
+ export type { CheckIssue, CheckIssueSeverity } from "./issues.js";
17
+ export type { AgentReadyCheckResult, AgentReadyFindings } from "./agent-ready.js";
18
+ export type { DnsCheckResult } from "./dns.js";
19
+ export type { RobotsTxtCheckResult } from "./robots-txt.js";
20
+ export type { SecurityHeadersCheckResult } from "./security-headers.js";
21
+ export type { SitemapCheckResult, SitemapCheckOptions } from "./sitemap.js";
22
+ export type { RedirectCheckResult, WwwAliasRedirectProbe } from "./redirect.js";
23
+ export type { Tier1ReportData } from "../analyze/report.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,QAAQ,EACR,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,CAAC;AAEF,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,OAAO,EACP,YAAY,EACZ,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAChF,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { CHECK_IDS, DEFAULT_CHECK_IDS } from "./types.js";
2
+ import { CHECK_DEFINITIONS, getCheckDefinition, listChecks, listImplementedChecks } from "./registry.js";
3
+ import { planCheckWaves, runCheck, runChecks } from "./runner.js";
4
+ import { runAnalyzeCheck } from "./analyze.js";
5
+ import { runCrawlCheck, prepareCrawlSeedUrls } from "./crawl.js";
6
+ import { analyzeAgentReady, runAgentReadyCheck } from "./agent-ready.js";
7
+ import { analyzeDnsRecords, runDnsCheck } from "./dns.js";
8
+ import { analyzeRobotsTxt, runRobotsTxtCheck } from "./robots-txt.js";
9
+ import { analyzeSecurityHeaders, runSecurityHeadersCheck } from "./security-headers.js";
10
+ import { analyzeSitemapCompare, runSitemapCheck } from "./sitemap.js";
11
+ import { analyzeRedirect, analyzeWwwAliasRedirect, buildAlternateHostUrl, resolveCanonicalUrl, resolveChecksForRedirect, runRedirectCheck, shouldCheckWwwAlias, toggleWwwHostname, } from "./redirect.js";
12
+ export { CHECK_IDS, CHECK_DEFINITIONS, DEFAULT_CHECK_IDS, getCheckDefinition, listChecks, listImplementedChecks, planCheckWaves, runAnalyzeCheck, runCheck, runChecks, runCrawlCheck, prepareCrawlSeedUrls, analyzeAgentReady, runAgentReadyCheck, analyzeDnsRecords, runDnsCheck, analyzeRobotsTxt, runRobotsTxtCheck, analyzeSecurityHeaders, runSecurityHeadersCheck, analyzeSitemapCompare, runSitemapCheck, analyzeRedirect, analyzeWwwAliasRedirect, buildAlternateHostUrl, resolveCanonicalUrl, resolveChecksForRedirect, runRedirectCheck, shouldCheckWwwAlias, toggleWwwHostname, };
@@ -0,0 +1,8 @@
1
+ export type CheckIssueSeverity = "critical" | "error" | "warning" | "review";
2
+ export type CheckIssue = {
3
+ id: string;
4
+ severity: CheckIssueSeverity;
5
+ message: string;
6
+ };
7
+ export declare function issue(id: string, severity: CheckIssueSeverity, message: string): CheckIssue;
8
+ //# sourceMappingURL=issues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/checks/issues.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7E,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAE3F"}
@@ -0,0 +1,3 @@
1
+ export function issue(id, severity, message) {
2
+ return { id, severity, message };
3
+ }
@@ -0,0 +1,38 @@
1
+ import { type CheckIssue } from "./issues.js";
2
+ import type { CheckId } from "./types.js";
3
+ export declare const REDIRECT_STATUS_CODES: Set<number>;
4
+ export declare const PERMANENT_REDIRECT_STATUS_CODES: Set<number>;
5
+ export type RedirectProbeInput = {
6
+ url: string;
7
+ status: number | null;
8
+ location: string | null;
9
+ resolvedTarget: string | null;
10
+ headers: Record<string, string>;
11
+ targetStatus: number | null;
12
+ };
13
+ export type WwwAliasRedirectProbe = {
14
+ canonicalUrl: string;
15
+ alternateUrl: string;
16
+ status: number | null;
17
+ location: string | null;
18
+ resolvedTarget: string | null;
19
+ targetStatus: number | null;
20
+ };
21
+ export type RedirectCheckResult = RedirectProbeInput & {
22
+ isRedirect: boolean;
23
+ redirectOnly: boolean;
24
+ issues: CheckIssue[];
25
+ wwwAlias?: WwwAliasRedirectProbe;
26
+ };
27
+ export declare function toggleWwwHostname(hostname: string): string;
28
+ export declare function buildAlternateHostUrl(url: string): string;
29
+ export declare function resolveCanonicalUrl(startUrl: string, probe: Pick<RedirectProbeInput, "resolvedTarget">): string;
30
+ export declare function shouldCheckWwwAlias(hostname: string): boolean;
31
+ export declare const REDIRECT_ONLY_CHECK_IDS: CheckId[];
32
+ export declare function analyzeRedirect(input: RedirectProbeInput): RedirectCheckResult;
33
+ export declare function analyzeWwwAliasRedirect(canonicalUrl: string, probe: Pick<RedirectProbeInput, "url" | "status" | "location" | "resolvedTarget" | "targetStatus">): CheckIssue[];
34
+ export declare function isDefaultCheckSet(requestedChecks: CheckId[]): boolean;
35
+ export declare function resolveChecksForRedirect(requestedChecks: CheckId[], redirect: Pick<RedirectCheckResult, "redirectOnly">): CheckId[];
36
+ export declare function checksSkippedByRedirect(requestedChecks: CheckId[], effectiveChecks: CheckId[]): CheckId[];
37
+ export declare function runRedirectCheck(startUrl: string, timeoutMs?: number): Promise<RedirectCheckResult>;
38
+ //# sourceMappingURL=redirect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/checks/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,eAAO,MAAM,qBAAqB,aAAqC,CAAC;AACxE,eAAO,MAAM,+BAA+B,aAAsB,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,MAAM,CAE/G;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM7D;AAED,eAAO,MAAM,uBAAuB,EAAE,OAAO,EAAuB,CAAC;AAmBrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAyD9E;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,gBAAgB,GAAG,cAAc,CAAC,GACjG,UAAU,EAAE,CAyEd;AAED,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,CAKrE;AAED,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,OAAO,EAAE,EAC1B,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,GAClD,OAAO,EAAE,CAWX;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAGzG;AA0DD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8BrH"}