@djangocfg/seo 2.1.109 → 2.1.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +42 -33
- package/dist/cli.mjs.map +1 -1
- package/dist/crawler/index.mjs +42 -33
- package/dist/crawler/index.mjs.map +1 -1
- package/dist/index.mjs +42 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/crawler/crawler.ts +27 -26
- package/src/crawler/sitemap-validator.ts +17 -14
package/dist/crawler/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseHTML, DOMParser } from 'linkedom';
|
|
2
2
|
import pLimit from 'p-limit';
|
|
3
3
|
import consola from 'consola';
|
|
4
4
|
import robotsParser from 'robots-parser';
|
|
@@ -113,14 +113,16 @@ var SiteCrawler = class {
|
|
|
113
113
|
* Parse HTML and extract SEO-relevant data
|
|
114
114
|
*/
|
|
115
115
|
parseHtml(html, result, pageUrl, depth) {
|
|
116
|
-
const
|
|
117
|
-
|
|
116
|
+
const { document } = parseHTML(html);
|
|
117
|
+
const titleEl = document.querySelector("title");
|
|
118
|
+
result.title = titleEl?.textContent?.trim() || void 0;
|
|
118
119
|
if (!result.title) {
|
|
119
120
|
result.warnings.push("Missing title tag");
|
|
120
121
|
} else if (result.title.length > 60) {
|
|
121
122
|
result.warnings.push(`Title too long (${result.title.length} chars, recommended: <60)`);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
const metaDesc = document.querySelector('meta[name="description"]');
|
|
125
|
+
result.metaDescription = metaDesc?.getAttribute("content")?.trim() || void 0;
|
|
124
126
|
if (!result.metaDescription) {
|
|
125
127
|
result.warnings.push("Missing meta description");
|
|
126
128
|
} else if (result.metaDescription.length > 160) {
|
|
@@ -128,25 +130,28 @@ var SiteCrawler = class {
|
|
|
128
130
|
`Meta description too long (${result.metaDescription.length} chars, recommended: <160)`
|
|
129
131
|
);
|
|
130
132
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
const metaRobots = document.querySelector('meta[name="robots"]');
|
|
134
|
+
result.metaRobots = metaRobots?.getAttribute("content")?.trim() || void 0;
|
|
135
|
+
const xRobots = document.querySelector('meta[http-equiv="X-Robots-Tag"]');
|
|
136
|
+
const xRobotsContent = xRobots?.getAttribute("content")?.trim();
|
|
137
|
+
if (xRobotsContent) {
|
|
138
|
+
result.metaRobots = result.metaRobots ? `${result.metaRobots}, ${xRobotsContent}` : xRobotsContent;
|
|
135
139
|
}
|
|
136
|
-
|
|
140
|
+
const canonical = document.querySelector('link[rel="canonical"]');
|
|
141
|
+
result.canonicalUrl = canonical?.getAttribute("href")?.trim() || void 0;
|
|
137
142
|
if (!result.canonicalUrl) {
|
|
138
143
|
result.warnings.push("Missing canonical tag");
|
|
139
144
|
}
|
|
140
|
-
result.h1 =
|
|
141
|
-
result.h2 =
|
|
145
|
+
result.h1 = Array.from(document.querySelectorAll("h1")).map((el) => el.textContent?.trim() || "");
|
|
146
|
+
result.h2 = Array.from(document.querySelectorAll("h2")).map((el) => el.textContent?.trim() || "");
|
|
142
147
|
if (result.h1.length === 0) {
|
|
143
148
|
result.warnings.push("Missing H1 tag");
|
|
144
149
|
} else if (result.h1.length > 1) {
|
|
145
150
|
result.warnings.push(`Multiple H1 tags (${result.h1.length})`);
|
|
146
151
|
}
|
|
147
|
-
|
|
148
|
-
const href =
|
|
149
|
-
if (!href)
|
|
152
|
+
for (const el of document.querySelectorAll("a[href]")) {
|
|
153
|
+
const href = el.getAttribute("href");
|
|
154
|
+
if (!href) continue;
|
|
150
155
|
try {
|
|
151
156
|
const linkUrl = new URL(href, pageUrl);
|
|
152
157
|
if (linkUrl.hostname === this.baseUrl.hostname) {
|
|
@@ -160,18 +165,19 @@ var SiteCrawler = class {
|
|
|
160
165
|
}
|
|
161
166
|
} catch {
|
|
162
167
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const src =
|
|
166
|
-
const alt =
|
|
168
|
+
}
|
|
169
|
+
for (const el of document.querySelectorAll("img")) {
|
|
170
|
+
const src = el.getAttribute("src");
|
|
171
|
+
const alt = el.getAttribute("alt");
|
|
172
|
+
const hasAltAttr = alt !== null;
|
|
167
173
|
if (src) {
|
|
168
174
|
result.images.push({
|
|
169
175
|
src,
|
|
170
|
-
alt,
|
|
171
|
-
hasAlt:
|
|
176
|
+
alt: alt ?? void 0,
|
|
177
|
+
hasAlt: hasAltAttr && alt.trim().length > 0
|
|
172
178
|
});
|
|
173
179
|
}
|
|
174
|
-
}
|
|
180
|
+
}
|
|
175
181
|
const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);
|
|
176
182
|
if (imagesWithoutAlt.length > 0) {
|
|
177
183
|
result.warnings.push(`${imagesWithoutAlt.length} images without alt text`);
|
|
@@ -517,28 +523,31 @@ async function analyzeSitemap(sitemapUrl) {
|
|
|
517
523
|
metadata: { contentType }
|
|
518
524
|
});
|
|
519
525
|
}
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
526
|
+
const parser = new DOMParser();
|
|
527
|
+
const doc = parser.parseFromString(content, "text/xml");
|
|
528
|
+
const sitemapIndex = doc.querySelector("sitemapindex");
|
|
529
|
+
if (sitemapIndex) {
|
|
523
530
|
analysis.type = "sitemap-index";
|
|
524
|
-
|
|
525
|
-
const loc =
|
|
531
|
+
for (const sitemap of doc.querySelectorAll("sitemap")) {
|
|
532
|
+
const loc = sitemap.querySelector("loc")?.textContent?.trim();
|
|
526
533
|
if (loc) {
|
|
527
534
|
analysis.childSitemaps.push(loc);
|
|
528
535
|
}
|
|
529
|
-
}
|
|
536
|
+
}
|
|
530
537
|
consola.debug(`Sitemap index contains ${analysis.childSitemaps.length} sitemaps`);
|
|
531
538
|
} else {
|
|
532
539
|
analysis.type = "sitemap";
|
|
533
|
-
|
|
534
|
-
const loc =
|
|
540
|
+
for (const url of doc.querySelectorAll("url")) {
|
|
541
|
+
const loc = url.querySelector("loc")?.textContent?.trim();
|
|
535
542
|
if (loc) {
|
|
536
543
|
analysis.urls.push(loc);
|
|
537
544
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
545
|
+
if (!analysis.lastmod) {
|
|
546
|
+
const lastmod = url.querySelector("lastmod")?.textContent?.trim();
|
|
547
|
+
if (lastmod) {
|
|
548
|
+
analysis.lastmod = lastmod;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
542
551
|
}
|
|
543
552
|
consola.debug(`Sitemap contains ${analysis.urls.length} URLs`);
|
|
544
553
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/crawler/crawler.ts","../../src/crawler/robots-parser.ts","../../src/crawler/sitemap-validator.ts"],"names":["hash","consola","load"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @djangocfg/seo - Site Crawler\n * Internal site crawler for SEO analysis\n */\n\nimport { load } from 'cheerio';\nimport pLimit from 'p-limit';\nimport consola from 'consola';\nimport type { CrawlResult, CrawlerConfig, SeoIssue } from '../types/index.js';\n\nconst DEFAULT_CONFIG: Required<CrawlerConfig> = {\n maxPages: 100,\n maxDepth: 3,\n concurrency: 5,\n timeout: 30000,\n userAgent: 'DjangoCFG-SEO-Crawler/1.0 (+https://djangocfg.com/bot)',\n respectRobotsTxt: true,\n includePatterns: [],\n excludePatterns: [\n '/api/',\n '/admin/',\n '/_next/',\n '/static/',\n '.pdf',\n '.jpg',\n '.png',\n '.gif',\n '.svg',\n '.css',\n '.js',\n ],\n};\n\nexport class SiteCrawler {\n private config: Required<CrawlerConfig>;\n private baseUrl: URL;\n private visited = new Set<string>();\n private queue: Array<{ url: string; depth: number }> = [];\n private results: CrawlResult[] = [];\n private limit: ReturnType<typeof pLimit>;\n\n constructor(siteUrl: string, config?: CrawlerConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.baseUrl = new URL(siteUrl);\n this.limit = pLimit(this.config.concurrency);\n }\n\n /**\n * Start crawling the site\n */\n async crawl(): Promise<CrawlResult[]> {\n consola.info(`Starting crawl of ${this.baseUrl.origin}`);\n consola.info(`Config: maxPages=${this.config.maxPages}, maxDepth=${this.config.maxDepth}`);\n\n this.queue.push({ url: this.baseUrl.href, depth: 0 });\n\n while (this.queue.length > 0 && this.results.length < this.config.maxPages) {\n const batch = this.queue.splice(0, this.config.concurrency);\n\n const promises = batch.map(({ url, depth }) =>\n this.limit(() => this.crawlPage(url, depth))\n );\n\n await Promise.all(promises);\n }\n\n consola.success(`Crawl complete. Crawled ${this.results.length} pages.`);\n return this.results;\n }\n\n /**\n * Crawl a single page\n */\n private async crawlPage(url: string, depth: number): Promise<void> {\n const normalizedUrl = this.normalizeUrl(url);\n\n if (this.visited.has(normalizedUrl)) return;\n if (this.shouldExclude(normalizedUrl)) return;\n\n this.visited.add(normalizedUrl);\n\n const startTime = Date.now();\n const result: CrawlResult = {\n url: normalizedUrl,\n statusCode: 0,\n links: { internal: [], external: [] },\n images: [],\n loadTime: 0,\n errors: [],\n warnings: [],\n crawledAt: new Date().toISOString(),\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n const response = await fetch(normalizedUrl, {\n headers: {\n 'User-Agent': this.config.userAgent,\n Accept: 'text/html,application/xhtml+xml',\n },\n signal: controller.signal,\n redirect: 'follow',\n });\n\n // TTFB = time from request start to first response headers\n result.ttfb = Date.now() - startTime;\n\n clearTimeout(timeoutId);\n\n result.statusCode = response.status;\n result.contentType = response.headers.get('content-type') || undefined;\n result.contentLength = Number(response.headers.get('content-length')) || undefined;\n\n if (response.ok && result.contentType?.includes('text/html')) {\n const html = await response.text();\n this.parseHtml(html, result, normalizedUrl, depth);\n } else if (!response.ok) {\n result.errors.push(`HTTP ${response.status}: ${response.statusText}`);\n }\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n result.errors.push('Request timeout');\n } else {\n result.errors.push(error.message);\n }\n }\n }\n\n result.loadTime = Date.now() - startTime;\n this.results.push(result);\n\n consola.debug(`Crawled: ${normalizedUrl} (${result.statusCode}) - ${result.loadTime}ms`);\n }\n\n /**\n * Parse HTML and extract SEO-relevant data\n */\n private parseHtml(html: string, result: CrawlResult, pageUrl: string, depth: number): void {\n const $ = load(html);\n\n // Title\n result.title = $('title').first().text().trim() || undefined;\n if (!result.title) {\n result.warnings.push('Missing title tag');\n } else if (result.title.length > 60) {\n result.warnings.push(`Title too long (${result.title.length} chars, recommended: <60)`);\n }\n\n // Meta description\n result.metaDescription =\n $('meta[name=\"description\"]').attr('content')?.trim() || undefined;\n if (!result.metaDescription) {\n result.warnings.push('Missing meta description');\n } else if (result.metaDescription.length > 160) {\n result.warnings.push(\n `Meta description too long (${result.metaDescription.length} chars, recommended: <160)`\n );\n }\n\n // Meta robots\n result.metaRobots = $('meta[name=\"robots\"]').attr('content')?.trim() || undefined;\n const xRobots = $('meta[http-equiv=\"X-Robots-Tag\"]').attr('content')?.trim();\n if (xRobots) {\n result.metaRobots = result.metaRobots ? `${result.metaRobots}, ${xRobots}` : xRobots;\n }\n\n // Canonical\n result.canonicalUrl = $('link[rel=\"canonical\"]').attr('href')?.trim() || undefined;\n if (!result.canonicalUrl) {\n result.warnings.push('Missing canonical tag');\n }\n\n // Headings\n result.h1 = $('h1')\n .map((_, el) => $(el).text().trim())\n .get();\n result.h2 = $('h2')\n .map((_, el) => $(el).text().trim())\n .get();\n\n if (result.h1.length === 0) {\n result.warnings.push('Missing H1 tag');\n } else if (result.h1.length > 1) {\n result.warnings.push(`Multiple H1 tags (${result.h1.length})`);\n }\n\n // Links\n $('a[href]').each((_, el) => {\n const href = $(el).attr('href');\n if (!href) return;\n\n try {\n const linkUrl = new URL(href, pageUrl);\n\n if (linkUrl.hostname === this.baseUrl.hostname) {\n const internalUrl = this.normalizeUrl(linkUrl.href);\n result.links.internal.push(internalUrl);\n\n // Add to crawl queue\n if (depth < this.config.maxDepth && !this.visited.has(internalUrl)) {\n this.queue.push({ url: internalUrl, depth: depth + 1 });\n }\n } else {\n result.links.external.push(linkUrl.href);\n }\n } catch {\n // Invalid URL, skip\n }\n });\n\n // Images\n $('img').each((_, el) => {\n const src = $(el).attr('src');\n const alt = $(el).attr('alt');\n\n if (src) {\n result.images.push({\n src,\n alt,\n hasAlt: alt !== undefined && alt.trim().length > 0,\n });\n }\n });\n\n const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);\n if (imagesWithoutAlt.length > 0) {\n result.warnings.push(`${imagesWithoutAlt.length} images without alt text`);\n }\n }\n\n /**\n * Normalize URL for deduplication\n */\n private normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url, this.baseUrl.href);\n // Remove trailing slash, hash, and sort query params\n parsed.hash = '';\n let pathname = parsed.pathname;\n if (pathname.endsWith('/') && pathname !== '/') {\n pathname = pathname.slice(0, -1);\n }\n parsed.pathname = pathname;\n return parsed.href;\n } catch {\n return url;\n }\n }\n\n /**\n * Check if URL should be excluded\n */\n private shouldExclude(url: string): boolean {\n // Check include patterns first\n if (this.config.includePatterns.length > 0) {\n const included = this.config.includePatterns.some((pattern) =>\n url.includes(pattern)\n );\n if (!included) return true;\n }\n\n // Check exclude patterns\n return this.config.excludePatterns.some((pattern) => url.includes(pattern));\n }\n}\n\n/**\n * Analyze crawl results for SEO issues\n */\nexport function analyzeCrawlResults(results: CrawlResult[]): SeoIssue[] {\n const issues: SeoIssue[] = [];\n\n for (const result of results) {\n // HTTP errors\n if (result.statusCode >= 400) {\n issues.push({\n id: `http-error-${hash(result.url)}`,\n url: result.url,\n category: 'technical',\n severity: result.statusCode >= 500 ? 'critical' : 'error',\n title: `HTTP ${result.statusCode} error`,\n description: `Page returns ${result.statusCode} status code.`,\n recommendation:\n result.statusCode === 404\n ? 'Either restore the content or set up a redirect.'\n : 'Fix the server error and ensure the page is accessible.',\n detectedAt: result.crawledAt,\n metadata: { statusCode: result.statusCode },\n });\n }\n\n // Missing title\n if (!result.title && result.statusCode === 200) {\n issues.push({\n id: `missing-title-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'error',\n title: 'Missing title tag',\n description: 'This page does not have a title tag.',\n recommendation: 'Add a unique, descriptive title tag (50-60 characters).',\n detectedAt: result.crawledAt,\n });\n }\n\n // Missing meta description\n if (!result.metaDescription && result.statusCode === 200) {\n issues.push({\n id: `missing-meta-desc-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Missing meta description',\n description: 'This page does not have a meta description.',\n recommendation: 'Add a unique meta description (120-160 characters).',\n detectedAt: result.crawledAt,\n });\n }\n\n // Missing H1\n if (result.h1 && result.h1.length === 0 && result.statusCode === 200) {\n issues.push({\n id: `missing-h1-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Missing H1 heading',\n description: 'This page does not have an H1 heading.',\n recommendation: 'Add a single H1 heading that describes the page content.',\n detectedAt: result.crawledAt,\n });\n }\n\n // Multiple H1s\n if (result.h1 && result.h1.length > 1) {\n issues.push({\n id: `multiple-h1-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Multiple H1 headings',\n description: `This page has ${result.h1.length} H1 headings.`,\n recommendation: 'Use only one H1 heading per page.',\n detectedAt: result.crawledAt,\n metadata: { h1Count: result.h1.length },\n });\n }\n\n // Images without alt\n const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);\n if (imagesWithoutAlt.length > 0) {\n issues.push({\n id: `images-no-alt-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'info',\n title: 'Images without alt text',\n description: `${imagesWithoutAlt.length} images are missing alt text.`,\n recommendation: 'Add descriptive alt text to all images for accessibility and SEO.',\n detectedAt: result.crawledAt,\n metadata: { count: imagesWithoutAlt.length },\n });\n }\n\n // Slow load time (> 3s)\n if (result.loadTime > 3000) {\n issues.push({\n id: `slow-page-${hash(result.url)}`,\n url: result.url,\n category: 'performance',\n severity: result.loadTime > 5000 ? 'error' : 'warning',\n title: 'Slow page load time',\n description: `Page took ${result.loadTime}ms to load.`,\n recommendation: 'Optimize page load time. Target under 3 seconds.',\n detectedAt: result.crawledAt,\n metadata: { loadTime: result.loadTime },\n });\n }\n\n // Slow TTFB (> 800ms)\n if (result.ttfb && result.ttfb > 800) {\n issues.push({\n id: `slow-ttfb-${hash(result.url)}`,\n url: result.url,\n category: 'performance',\n severity: result.ttfb > 1500 ? 'error' : 'warning',\n title: 'Slow Time to First Byte',\n description: `TTFB is ${result.ttfb}ms. Server responded slowly.`,\n recommendation: 'Optimize server response. Target TTFB under 800ms. Consider CDN, caching, or server upgrades.',\n detectedAt: result.crawledAt,\n metadata: { ttfb: result.ttfb },\n });\n }\n\n // Noindex check\n if (result.metaRobots?.includes('noindex')) {\n issues.push({\n id: `noindex-${hash(result.url)}`,\n url: result.url,\n category: 'indexing',\n severity: 'info',\n title: 'Page marked as noindex',\n description: 'This page has a noindex directive.',\n recommendation: 'Verify this is intentional. Remove noindex if the page should be indexed.',\n detectedAt: result.crawledAt,\n metadata: { metaRobots: result.metaRobots },\n });\n }\n }\n\n return issues;\n}\n\nfunction hash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return Math.abs(hash).toString(36);\n}\n","/**\n * @djangocfg/seo - Robots.txt Parser\n * Parse and analyze robots.txt files\n */\n\nimport robotsParser from 'robots-parser';\nimport consola from 'consola';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface RobotsAnalysis {\n exists: boolean;\n content?: string;\n sitemaps: string[];\n allowedPaths: string[];\n disallowedPaths: string[];\n crawlDelay?: number;\n issues: SeoIssue[];\n}\n\n/**\n * Fetch and parse robots.txt for a site\n */\nexport async function analyzeRobotsTxt(siteUrl: string): Promise<RobotsAnalysis> {\n const robotsUrl = new URL('/robots.txt', siteUrl).href;\n\n const analysis: RobotsAnalysis = {\n exists: false,\n sitemaps: [],\n allowedPaths: [],\n disallowedPaths: [],\n issues: [],\n };\n\n try {\n const response = await fetch(robotsUrl);\n\n if (!response.ok) {\n analysis.issues.push({\n id: 'missing-robots-txt',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Missing robots.txt',\n description: `No robots.txt file found (HTTP ${response.status}).`,\n recommendation: 'Create a robots.txt file to control crawler access.',\n detectedAt: new Date().toISOString(),\n });\n return analysis;\n }\n\n analysis.exists = true;\n analysis.content = await response.text();\n\n // Check for Cloudflare managed robots.txt override\n if (\n analysis.content.includes('content-signal') ||\n analysis.content.includes('Content-Signal') ||\n analysis.content.includes('ai-input') ||\n analysis.content.includes('ai-train')\n ) {\n analysis.issues.push({\n id: 'cloudflare-managed-robots',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Cloudflare managed robots.txt detected',\n description:\n 'Your robots.txt is being overwritten by Cloudflare\\'s \"Content Signals Policy\". ' +\n 'Your app/robots.ts file is not being served.',\n recommendation:\n 'Disable in Cloudflare Dashboard: Security → Settings → \"Manage your robots.txt\" → Set to \"Off\".',\n detectedAt: new Date().toISOString(),\n metadata: {\n cloudflareFeature: 'Managed robots.txt',\n docsUrl: 'https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/',\n },\n });\n }\n\n // Parse robots.txt\n const robots = robotsParser(robotsUrl, analysis.content);\n\n // Extract sitemaps\n analysis.sitemaps = robots.getSitemaps();\n\n if (analysis.sitemaps.length === 0) {\n analysis.issues.push({\n id: 'no-sitemap-in-robots',\n url: robotsUrl,\n category: 'technical',\n severity: 'info',\n title: 'No sitemap in robots.txt',\n description: 'No sitemap URL is declared in robots.txt.',\n recommendation: 'Add a Sitemap directive pointing to your XML sitemap.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n // Parse rules (simplified extraction)\n const lines = analysis.content.split('\\n');\n let currentUserAgent = '*';\n\n for (const line of lines) {\n const trimmed = line.trim().toLowerCase();\n\n if (trimmed.startsWith('user-agent:')) {\n currentUserAgent = trimmed.replace('user-agent:', '').trim();\n } else if (trimmed.startsWith('disallow:')) {\n const path = line.trim().replace(/disallow:/i, '').trim();\n if (path) {\n analysis.disallowedPaths.push(path);\n }\n } else if (trimmed.startsWith('allow:')) {\n const path = line.trim().replace(/allow:/i, '').trim();\n if (path) {\n analysis.allowedPaths.push(path);\n }\n } else if (trimmed.startsWith('crawl-delay:')) {\n const delay = parseInt(trimmed.replace('crawl-delay:', '').trim(), 10);\n if (!isNaN(delay)) {\n analysis.crawlDelay = delay;\n }\n }\n }\n\n // Check for blocking important paths\n const importantPaths = ['/', '/sitemap.xml'];\n for (const path of importantPaths) {\n if (!robots.isAllowed(new URL(path, siteUrl).href, 'Googlebot')) {\n analysis.issues.push({\n id: `blocked-important-path-${path.replace(/\\//g, '-')}`,\n url: siteUrl,\n category: 'crawling',\n severity: 'error',\n title: `Important path blocked: ${path}`,\n description: `The path ${path} is blocked in robots.txt.`,\n recommendation: `Ensure ${path} is accessible to search engines.`,\n detectedAt: new Date().toISOString(),\n metadata: { path },\n });\n }\n }\n\n // Check for excessively restrictive rules\n if (analysis.disallowedPaths.includes('/')) {\n analysis.issues.push({\n id: 'all-blocked',\n url: robotsUrl,\n category: 'crawling',\n severity: 'critical',\n title: 'Entire site blocked',\n description: 'robots.txt blocks access to the entire site (Disallow: /).',\n recommendation: 'Remove or modify this rule if you want your site to be indexed.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n consola.debug(`Analyzed robots.txt: ${analysis.disallowedPaths.length} disallow rules`);\n } catch (error) {\n consola.error('Failed to fetch robots.txt:', error);\n analysis.issues.push({\n id: 'robots-txt-error',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Failed to fetch robots.txt',\n description: `Error fetching robots.txt: ${error instanceof Error ? error.message : 'Unknown error'}`,\n recommendation: 'Ensure robots.txt is accessible.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n return analysis;\n}\n\n/**\n * Check if a URL is allowed by robots.txt\n */\nexport async function isUrlAllowed(\n siteUrl: string,\n url: string,\n userAgent = 'Googlebot'\n): Promise<boolean> {\n const robotsUrl = new URL('/robots.txt', siteUrl).href;\n\n try {\n const response = await fetch(robotsUrl);\n if (!response.ok) return true; // No robots.txt = allow all\n\n const content = await response.text();\n const robots = robotsParser(robotsUrl, content);\n\n return robots.isAllowed(url, userAgent) ?? true;\n } catch {\n return true; // Error fetching = allow\n }\n}\n","/**\n * @djangocfg/seo - Sitemap Validator\n * Validate XML sitemaps\n */\n\nimport { load } from 'cheerio';\nimport consola from 'consola';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface SitemapAnalysis {\n url: string;\n exists: boolean;\n type: 'sitemap' | 'sitemap-index' | 'unknown';\n urls: string[];\n childSitemaps: string[];\n lastmod?: string;\n issues: SeoIssue[];\n}\n\n/**\n * Analyze a sitemap URL\n */\nexport async function analyzeSitemap(sitemapUrl: string): Promise<SitemapAnalysis> {\n const analysis: SitemapAnalysis = {\n url: sitemapUrl,\n exists: false,\n type: 'unknown',\n urls: [],\n childSitemaps: [],\n issues: [],\n };\n\n try {\n const response = await fetch(sitemapUrl, {\n headers: {\n Accept: 'application/xml, text/xml, */*',\n },\n });\n\n if (!response.ok) {\n analysis.issues.push({\n id: `sitemap-not-found-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap not accessible',\n description: `Sitemap returned HTTP ${response.status}.`,\n recommendation: 'Ensure the sitemap URL is correct and accessible.',\n detectedAt: new Date().toISOString(),\n metadata: { statusCode: response.status },\n });\n return analysis;\n }\n\n analysis.exists = true;\n const content = await response.text();\n\n // Check content type\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.includes('xml') && !content.trim().startsWith('<?xml')) {\n analysis.issues.push({\n id: `sitemap-not-xml-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Sitemap is not XML',\n description: 'The sitemap does not have an XML content type.',\n recommendation: 'Ensure sitemap is served with Content-Type: application/xml.',\n detectedAt: new Date().toISOString(),\n metadata: { contentType },\n });\n }\n\n // Parse XML\n const $ = load(content, { xmlMode: true });\n\n // Check if it's a sitemap index\n const sitemapIndex = $('sitemapindex');\n if (sitemapIndex.length > 0) {\n analysis.type = 'sitemap-index';\n\n $('sitemap').each((_, el) => {\n const loc = $('loc', el).text().trim();\n if (loc) {\n analysis.childSitemaps.push(loc);\n }\n });\n\n consola.debug(`Sitemap index contains ${analysis.childSitemaps.length} sitemaps`);\n } else {\n analysis.type = 'sitemap';\n\n $('url').each((_, el) => {\n const loc = $('loc', el).text().trim();\n if (loc) {\n analysis.urls.push(loc);\n }\n });\n\n const lastmod = $('url lastmod').first().text().trim();\n if (lastmod) {\n analysis.lastmod = lastmod;\n }\n\n consola.debug(`Sitemap contains ${analysis.urls.length} URLs`);\n }\n\n // Validate sitemap content\n if (analysis.type === 'sitemap' && analysis.urls.length === 0) {\n analysis.issues.push({\n id: `sitemap-empty-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Sitemap is empty',\n description: 'The sitemap contains no URLs.',\n recommendation: 'Add URLs to your sitemap or remove it if not needed.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n // Check for too many URLs (Google limit is 50,000)\n if (analysis.urls.length > 50000) {\n analysis.issues.push({\n id: `sitemap-too-large-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap exceeds URL limit',\n description: `Sitemap contains ${analysis.urls.length} URLs. Maximum is 50,000.`,\n recommendation: 'Split the sitemap into multiple files using a sitemap index.',\n detectedAt: new Date().toISOString(),\n metadata: { urlCount: analysis.urls.length },\n });\n }\n\n // Check file size (Google limit is 50MB uncompressed)\n const sizeInMB = new Blob([content]).size / (1024 * 1024);\n if (sizeInMB > 50) {\n analysis.issues.push({\n id: `sitemap-too-large-size-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap exceeds size limit',\n description: `Sitemap is ${sizeInMB.toFixed(2)}MB. Maximum is 50MB.`,\n recommendation: 'Split the sitemap or compress it.',\n detectedAt: new Date().toISOString(),\n metadata: { sizeMB: sizeInMB },\n });\n }\n } catch (error) {\n consola.error('Failed to analyze sitemap:', error);\n analysis.issues.push({\n id: `sitemap-error-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Failed to parse sitemap',\n description: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n recommendation: 'Check sitemap validity using Google Search Console.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n return analysis;\n}\n\n/**\n * Recursively analyze a sitemap and all its children\n */\nexport async function analyzeAllSitemaps(\n sitemapUrl: string,\n maxDepth = 3\n): Promise<SitemapAnalysis[]> {\n const results: SitemapAnalysis[] = [];\n const visited = new Set<string>();\n\n async function analyze(url: string, depth: number): Promise<void> {\n if (depth > maxDepth || visited.has(url)) return;\n visited.add(url);\n\n const analysis = await analyzeSitemap(url);\n results.push(analysis);\n\n // Recursively analyze child sitemaps\n for (const childUrl of analysis.childSitemaps) {\n await analyze(childUrl, depth + 1);\n }\n }\n\n await analyze(sitemapUrl, 0);\n return results;\n}\n\nfunction hash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return Math.abs(hash).toString(36);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/crawler/crawler.ts","../../src/crawler/robots-parser.ts","../../src/crawler/sitemap-validator.ts"],"names":["hash","consola"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @djangocfg/seo - Site Crawler\n * Internal site crawler for SEO analysis\n */\n\nimport { parseHTML } from 'linkedom';\nimport pLimit from 'p-limit';\nimport consola from 'consola';\nimport type { CrawlResult, CrawlerConfig, SeoIssue } from '../types/index.js';\n\nconst DEFAULT_CONFIG: Required<CrawlerConfig> = {\n maxPages: 100,\n maxDepth: 3,\n concurrency: 5,\n timeout: 30000,\n userAgent: 'DjangoCFG-SEO-Crawler/1.0 (+https://djangocfg.com/bot)',\n respectRobotsTxt: true,\n includePatterns: [],\n excludePatterns: [\n '/api/',\n '/admin/',\n '/_next/',\n '/static/',\n '.pdf',\n '.jpg',\n '.png',\n '.gif',\n '.svg',\n '.css',\n '.js',\n ],\n};\n\nexport class SiteCrawler {\n private config: Required<CrawlerConfig>;\n private baseUrl: URL;\n private visited = new Set<string>();\n private queue: Array<{ url: string; depth: number }> = [];\n private results: CrawlResult[] = [];\n private limit: ReturnType<typeof pLimit>;\n\n constructor(siteUrl: string, config?: CrawlerConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.baseUrl = new URL(siteUrl);\n this.limit = pLimit(this.config.concurrency);\n }\n\n /**\n * Start crawling the site\n */\n async crawl(): Promise<CrawlResult[]> {\n consola.info(`Starting crawl of ${this.baseUrl.origin}`);\n consola.info(`Config: maxPages=${this.config.maxPages}, maxDepth=${this.config.maxDepth}`);\n\n this.queue.push({ url: this.baseUrl.href, depth: 0 });\n\n while (this.queue.length > 0 && this.results.length < this.config.maxPages) {\n const batch = this.queue.splice(0, this.config.concurrency);\n\n const promises = batch.map(({ url, depth }) =>\n this.limit(() => this.crawlPage(url, depth))\n );\n\n await Promise.all(promises);\n }\n\n consola.success(`Crawl complete. Crawled ${this.results.length} pages.`);\n return this.results;\n }\n\n /**\n * Crawl a single page\n */\n private async crawlPage(url: string, depth: number): Promise<void> {\n const normalizedUrl = this.normalizeUrl(url);\n\n if (this.visited.has(normalizedUrl)) return;\n if (this.shouldExclude(normalizedUrl)) return;\n\n this.visited.add(normalizedUrl);\n\n const startTime = Date.now();\n const result: CrawlResult = {\n url: normalizedUrl,\n statusCode: 0,\n links: { internal: [], external: [] },\n images: [],\n loadTime: 0,\n errors: [],\n warnings: [],\n crawledAt: new Date().toISOString(),\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n const response = await fetch(normalizedUrl, {\n headers: {\n 'User-Agent': this.config.userAgent,\n Accept: 'text/html,application/xhtml+xml',\n },\n signal: controller.signal,\n redirect: 'follow',\n });\n\n // TTFB = time from request start to first response headers\n result.ttfb = Date.now() - startTime;\n\n clearTimeout(timeoutId);\n\n result.statusCode = response.status;\n result.contentType = response.headers.get('content-type') || undefined;\n result.contentLength = Number(response.headers.get('content-length')) || undefined;\n\n if (response.ok && result.contentType?.includes('text/html')) {\n const html = await response.text();\n this.parseHtml(html, result, normalizedUrl, depth);\n } else if (!response.ok) {\n result.errors.push(`HTTP ${response.status}: ${response.statusText}`);\n }\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n result.errors.push('Request timeout');\n } else {\n result.errors.push(error.message);\n }\n }\n }\n\n result.loadTime = Date.now() - startTime;\n this.results.push(result);\n\n consola.debug(`Crawled: ${normalizedUrl} (${result.statusCode}) - ${result.loadTime}ms`);\n }\n\n /**\n * Parse HTML and extract SEO-relevant data\n */\n private parseHtml(html: string, result: CrawlResult, pageUrl: string, depth: number): void {\n const { document } = parseHTML(html);\n\n // Title\n const titleEl = document.querySelector('title');\n result.title = titleEl?.textContent?.trim() || undefined;\n if (!result.title) {\n result.warnings.push('Missing title tag');\n } else if (result.title.length > 60) {\n result.warnings.push(`Title too long (${result.title.length} chars, recommended: <60)`);\n }\n\n // Meta description\n const metaDesc = document.querySelector('meta[name=\"description\"]');\n result.metaDescription = metaDesc?.getAttribute('content')?.trim() || undefined;\n if (!result.metaDescription) {\n result.warnings.push('Missing meta description');\n } else if (result.metaDescription.length > 160) {\n result.warnings.push(\n `Meta description too long (${result.metaDescription.length} chars, recommended: <160)`\n );\n }\n\n // Meta robots\n const metaRobots = document.querySelector('meta[name=\"robots\"]');\n result.metaRobots = metaRobots?.getAttribute('content')?.trim() || undefined;\n const xRobots = document.querySelector('meta[http-equiv=\"X-Robots-Tag\"]');\n const xRobotsContent = xRobots?.getAttribute('content')?.trim();\n if (xRobotsContent) {\n result.metaRobots = result.metaRobots ? `${result.metaRobots}, ${xRobotsContent}` : xRobotsContent;\n }\n\n // Canonical\n const canonical = document.querySelector('link[rel=\"canonical\"]');\n result.canonicalUrl = canonical?.getAttribute('href')?.trim() || undefined;\n if (!result.canonicalUrl) {\n result.warnings.push('Missing canonical tag');\n }\n\n // Headings\n result.h1 = Array.from(document.querySelectorAll('h1')).map((el) => (el as { textContent?: string | null }).textContent?.trim() || '');\n result.h2 = Array.from(document.querySelectorAll('h2')).map((el) => (el as { textContent?: string | null }).textContent?.trim() || '');\n\n if (result.h1.length === 0) {\n result.warnings.push('Missing H1 tag');\n } else if (result.h1.length > 1) {\n result.warnings.push(`Multiple H1 tags (${result.h1.length})`);\n }\n\n // Links\n for (const el of document.querySelectorAll('a[href]')) {\n const href = el.getAttribute('href');\n if (!href) continue;\n\n try {\n const linkUrl = new URL(href, pageUrl);\n\n if (linkUrl.hostname === this.baseUrl.hostname) {\n const internalUrl = this.normalizeUrl(linkUrl.href);\n result.links.internal.push(internalUrl);\n\n // Add to crawl queue\n if (depth < this.config.maxDepth && !this.visited.has(internalUrl)) {\n this.queue.push({ url: internalUrl, depth: depth + 1 });\n }\n } else {\n result.links.external.push(linkUrl.href);\n }\n } catch {\n // Invalid URL, skip\n }\n }\n\n // Images\n for (const el of document.querySelectorAll('img')) {\n const src = el.getAttribute('src');\n const alt = el.getAttribute('alt');\n const hasAltAttr = alt !== null;\n\n if (src) {\n result.images.push({\n src,\n alt: alt ?? undefined,\n hasAlt: hasAltAttr && alt.trim().length > 0,\n });\n }\n }\n\n const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);\n if (imagesWithoutAlt.length > 0) {\n result.warnings.push(`${imagesWithoutAlt.length} images without alt text`);\n }\n }\n\n /**\n * Normalize URL for deduplication\n */\n private normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url, this.baseUrl.href);\n // Remove trailing slash, hash, and sort query params\n parsed.hash = '';\n let pathname = parsed.pathname;\n if (pathname.endsWith('/') && pathname !== '/') {\n pathname = pathname.slice(0, -1);\n }\n parsed.pathname = pathname;\n return parsed.href;\n } catch {\n return url;\n }\n }\n\n /**\n * Check if URL should be excluded\n */\n private shouldExclude(url: string): boolean {\n // Check include patterns first\n if (this.config.includePatterns.length > 0) {\n const included = this.config.includePatterns.some((pattern) =>\n url.includes(pattern)\n );\n if (!included) return true;\n }\n\n // Check exclude patterns\n return this.config.excludePatterns.some((pattern) => url.includes(pattern));\n }\n}\n\n/**\n * Analyze crawl results for SEO issues\n */\nexport function analyzeCrawlResults(results: CrawlResult[]): SeoIssue[] {\n const issues: SeoIssue[] = [];\n\n for (const result of results) {\n // HTTP errors\n if (result.statusCode >= 400) {\n issues.push({\n id: `http-error-${hash(result.url)}`,\n url: result.url,\n category: 'technical',\n severity: result.statusCode >= 500 ? 'critical' : 'error',\n title: `HTTP ${result.statusCode} error`,\n description: `Page returns ${result.statusCode} status code.`,\n recommendation:\n result.statusCode === 404\n ? 'Either restore the content or set up a redirect.'\n : 'Fix the server error and ensure the page is accessible.',\n detectedAt: result.crawledAt,\n metadata: { statusCode: result.statusCode },\n });\n }\n\n // Missing title\n if (!result.title && result.statusCode === 200) {\n issues.push({\n id: `missing-title-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'error',\n title: 'Missing title tag',\n description: 'This page does not have a title tag.',\n recommendation: 'Add a unique, descriptive title tag (50-60 characters).',\n detectedAt: result.crawledAt,\n });\n }\n\n // Missing meta description\n if (!result.metaDescription && result.statusCode === 200) {\n issues.push({\n id: `missing-meta-desc-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Missing meta description',\n description: 'This page does not have a meta description.',\n recommendation: 'Add a unique meta description (120-160 characters).',\n detectedAt: result.crawledAt,\n });\n }\n\n // Missing H1\n if (result.h1 && result.h1.length === 0 && result.statusCode === 200) {\n issues.push({\n id: `missing-h1-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Missing H1 heading',\n description: 'This page does not have an H1 heading.',\n recommendation: 'Add a single H1 heading that describes the page content.',\n detectedAt: result.crawledAt,\n });\n }\n\n // Multiple H1s\n if (result.h1 && result.h1.length > 1) {\n issues.push({\n id: `multiple-h1-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'warning',\n title: 'Multiple H1 headings',\n description: `This page has ${result.h1.length} H1 headings.`,\n recommendation: 'Use only one H1 heading per page.',\n detectedAt: result.crawledAt,\n metadata: { h1Count: result.h1.length },\n });\n }\n\n // Images without alt\n const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);\n if (imagesWithoutAlt.length > 0) {\n issues.push({\n id: `images-no-alt-${hash(result.url)}`,\n url: result.url,\n category: 'content',\n severity: 'info',\n title: 'Images without alt text',\n description: `${imagesWithoutAlt.length} images are missing alt text.`,\n recommendation: 'Add descriptive alt text to all images for accessibility and SEO.',\n detectedAt: result.crawledAt,\n metadata: { count: imagesWithoutAlt.length },\n });\n }\n\n // Slow load time (> 3s)\n if (result.loadTime > 3000) {\n issues.push({\n id: `slow-page-${hash(result.url)}`,\n url: result.url,\n category: 'performance',\n severity: result.loadTime > 5000 ? 'error' : 'warning',\n title: 'Slow page load time',\n description: `Page took ${result.loadTime}ms to load.`,\n recommendation: 'Optimize page load time. Target under 3 seconds.',\n detectedAt: result.crawledAt,\n metadata: { loadTime: result.loadTime },\n });\n }\n\n // Slow TTFB (> 800ms)\n if (result.ttfb && result.ttfb > 800) {\n issues.push({\n id: `slow-ttfb-${hash(result.url)}`,\n url: result.url,\n category: 'performance',\n severity: result.ttfb > 1500 ? 'error' : 'warning',\n title: 'Slow Time to First Byte',\n description: `TTFB is ${result.ttfb}ms. Server responded slowly.`,\n recommendation: 'Optimize server response. Target TTFB under 800ms. Consider CDN, caching, or server upgrades.',\n detectedAt: result.crawledAt,\n metadata: { ttfb: result.ttfb },\n });\n }\n\n // Noindex check\n if (result.metaRobots?.includes('noindex')) {\n issues.push({\n id: `noindex-${hash(result.url)}`,\n url: result.url,\n category: 'indexing',\n severity: 'info',\n title: 'Page marked as noindex',\n description: 'This page has a noindex directive.',\n recommendation: 'Verify this is intentional. Remove noindex if the page should be indexed.',\n detectedAt: result.crawledAt,\n metadata: { metaRobots: result.metaRobots },\n });\n }\n }\n\n return issues;\n}\n\nfunction hash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return Math.abs(hash).toString(36);\n}\n","/**\n * @djangocfg/seo - Robots.txt Parser\n * Parse and analyze robots.txt files\n */\n\nimport robotsParser from 'robots-parser';\nimport consola from 'consola';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface RobotsAnalysis {\n exists: boolean;\n content?: string;\n sitemaps: string[];\n allowedPaths: string[];\n disallowedPaths: string[];\n crawlDelay?: number;\n issues: SeoIssue[];\n}\n\n/**\n * Fetch and parse robots.txt for a site\n */\nexport async function analyzeRobotsTxt(siteUrl: string): Promise<RobotsAnalysis> {\n const robotsUrl = new URL('/robots.txt', siteUrl).href;\n\n const analysis: RobotsAnalysis = {\n exists: false,\n sitemaps: [],\n allowedPaths: [],\n disallowedPaths: [],\n issues: [],\n };\n\n try {\n const response = await fetch(robotsUrl);\n\n if (!response.ok) {\n analysis.issues.push({\n id: 'missing-robots-txt',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Missing robots.txt',\n description: `No robots.txt file found (HTTP ${response.status}).`,\n recommendation: 'Create a robots.txt file to control crawler access.',\n detectedAt: new Date().toISOString(),\n });\n return analysis;\n }\n\n analysis.exists = true;\n analysis.content = await response.text();\n\n // Check for Cloudflare managed robots.txt override\n if (\n analysis.content.includes('content-signal') ||\n analysis.content.includes('Content-Signal') ||\n analysis.content.includes('ai-input') ||\n analysis.content.includes('ai-train')\n ) {\n analysis.issues.push({\n id: 'cloudflare-managed-robots',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Cloudflare managed robots.txt detected',\n description:\n 'Your robots.txt is being overwritten by Cloudflare\\'s \"Content Signals Policy\". ' +\n 'Your app/robots.ts file is not being served.',\n recommendation:\n 'Disable in Cloudflare Dashboard: Security → Settings → \"Manage your robots.txt\" → Set to \"Off\".',\n detectedAt: new Date().toISOString(),\n metadata: {\n cloudflareFeature: 'Managed robots.txt',\n docsUrl: 'https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/',\n },\n });\n }\n\n // Parse robots.txt\n const robots = robotsParser(robotsUrl, analysis.content);\n\n // Extract sitemaps\n analysis.sitemaps = robots.getSitemaps();\n\n if (analysis.sitemaps.length === 0) {\n analysis.issues.push({\n id: 'no-sitemap-in-robots',\n url: robotsUrl,\n category: 'technical',\n severity: 'info',\n title: 'No sitemap in robots.txt',\n description: 'No sitemap URL is declared in robots.txt.',\n recommendation: 'Add a Sitemap directive pointing to your XML sitemap.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n // Parse rules (simplified extraction)\n const lines = analysis.content.split('\\n');\n let currentUserAgent = '*';\n\n for (const line of lines) {\n const trimmed = line.trim().toLowerCase();\n\n if (trimmed.startsWith('user-agent:')) {\n currentUserAgent = trimmed.replace('user-agent:', '').trim();\n } else if (trimmed.startsWith('disallow:')) {\n const path = line.trim().replace(/disallow:/i, '').trim();\n if (path) {\n analysis.disallowedPaths.push(path);\n }\n } else if (trimmed.startsWith('allow:')) {\n const path = line.trim().replace(/allow:/i, '').trim();\n if (path) {\n analysis.allowedPaths.push(path);\n }\n } else if (trimmed.startsWith('crawl-delay:')) {\n const delay = parseInt(trimmed.replace('crawl-delay:', '').trim(), 10);\n if (!isNaN(delay)) {\n analysis.crawlDelay = delay;\n }\n }\n }\n\n // Check for blocking important paths\n const importantPaths = ['/', '/sitemap.xml'];\n for (const path of importantPaths) {\n if (!robots.isAllowed(new URL(path, siteUrl).href, 'Googlebot')) {\n analysis.issues.push({\n id: `blocked-important-path-${path.replace(/\\//g, '-')}`,\n url: siteUrl,\n category: 'crawling',\n severity: 'error',\n title: `Important path blocked: ${path}`,\n description: `The path ${path} is blocked in robots.txt.`,\n recommendation: `Ensure ${path} is accessible to search engines.`,\n detectedAt: new Date().toISOString(),\n metadata: { path },\n });\n }\n }\n\n // Check for excessively restrictive rules\n if (analysis.disallowedPaths.includes('/')) {\n analysis.issues.push({\n id: 'all-blocked',\n url: robotsUrl,\n category: 'crawling',\n severity: 'critical',\n title: 'Entire site blocked',\n description: 'robots.txt blocks access to the entire site (Disallow: /).',\n recommendation: 'Remove or modify this rule if you want your site to be indexed.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n consola.debug(`Analyzed robots.txt: ${analysis.disallowedPaths.length} disallow rules`);\n } catch (error) {\n consola.error('Failed to fetch robots.txt:', error);\n analysis.issues.push({\n id: 'robots-txt-error',\n url: robotsUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Failed to fetch robots.txt',\n description: `Error fetching robots.txt: ${error instanceof Error ? error.message : 'Unknown error'}`,\n recommendation: 'Ensure robots.txt is accessible.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n return analysis;\n}\n\n/**\n * Check if a URL is allowed by robots.txt\n */\nexport async function isUrlAllowed(\n siteUrl: string,\n url: string,\n userAgent = 'Googlebot'\n): Promise<boolean> {\n const robotsUrl = new URL('/robots.txt', siteUrl).href;\n\n try {\n const response = await fetch(robotsUrl);\n if (!response.ok) return true; // No robots.txt = allow all\n\n const content = await response.text();\n const robots = robotsParser(robotsUrl, content);\n\n return robots.isAllowed(url, userAgent) ?? true;\n } catch {\n return true; // Error fetching = allow\n }\n}\n","/**\n * @djangocfg/seo - Sitemap Validator\n * Validate XML sitemaps\n */\n\nimport { DOMParser } from 'linkedom';\nimport consola from 'consola';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface SitemapAnalysis {\n url: string;\n exists: boolean;\n type: 'sitemap' | 'sitemap-index' | 'unknown';\n urls: string[];\n childSitemaps: string[];\n lastmod?: string;\n issues: SeoIssue[];\n}\n\n/**\n * Analyze a sitemap URL\n */\nexport async function analyzeSitemap(sitemapUrl: string): Promise<SitemapAnalysis> {\n const analysis: SitemapAnalysis = {\n url: sitemapUrl,\n exists: false,\n type: 'unknown',\n urls: [],\n childSitemaps: [],\n issues: [],\n };\n\n try {\n const response = await fetch(sitemapUrl, {\n headers: {\n Accept: 'application/xml, text/xml, */*',\n },\n });\n\n if (!response.ok) {\n analysis.issues.push({\n id: `sitemap-not-found-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap not accessible',\n description: `Sitemap returned HTTP ${response.status}.`,\n recommendation: 'Ensure the sitemap URL is correct and accessible.',\n detectedAt: new Date().toISOString(),\n metadata: { statusCode: response.status },\n });\n return analysis;\n }\n\n analysis.exists = true;\n const content = await response.text();\n\n // Check content type\n const contentType = response.headers.get('content-type') || '';\n if (!contentType.includes('xml') && !content.trim().startsWith('<?xml')) {\n analysis.issues.push({\n id: `sitemap-not-xml-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Sitemap is not XML',\n description: 'The sitemap does not have an XML content type.',\n recommendation: 'Ensure sitemap is served with Content-Type: application/xml.',\n detectedAt: new Date().toISOString(),\n metadata: { contentType },\n });\n }\n\n // Parse XML\n const parser = new DOMParser();\n const doc = parser.parseFromString(content, 'text/xml');\n\n // Check if it's a sitemap index\n const sitemapIndex = doc.querySelector('sitemapindex');\n if (sitemapIndex) {\n analysis.type = 'sitemap-index';\n\n for (const sitemap of doc.querySelectorAll('sitemap')) {\n const loc = sitemap.querySelector('loc')?.textContent?.trim();\n if (loc) {\n analysis.childSitemaps.push(loc);\n }\n }\n\n consola.debug(`Sitemap index contains ${analysis.childSitemaps.length} sitemaps`);\n } else {\n analysis.type = 'sitemap';\n\n for (const url of doc.querySelectorAll('url')) {\n const loc = url.querySelector('loc')?.textContent?.trim();\n if (loc) {\n analysis.urls.push(loc);\n }\n // Get lastmod from first URL\n if (!analysis.lastmod) {\n const lastmod = url.querySelector('lastmod')?.textContent?.trim();\n if (lastmod) {\n analysis.lastmod = lastmod;\n }\n }\n }\n\n consola.debug(`Sitemap contains ${analysis.urls.length} URLs`);\n }\n\n // Validate sitemap content\n if (analysis.type === 'sitemap' && analysis.urls.length === 0) {\n analysis.issues.push({\n id: `sitemap-empty-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'warning',\n title: 'Sitemap is empty',\n description: 'The sitemap contains no URLs.',\n recommendation: 'Add URLs to your sitemap or remove it if not needed.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n // Check for too many URLs (Google limit is 50,000)\n if (analysis.urls.length > 50000) {\n analysis.issues.push({\n id: `sitemap-too-large-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap exceeds URL limit',\n description: `Sitemap contains ${analysis.urls.length} URLs. Maximum is 50,000.`,\n recommendation: 'Split the sitemap into multiple files using a sitemap index.',\n detectedAt: new Date().toISOString(),\n metadata: { urlCount: analysis.urls.length },\n });\n }\n\n // Check file size (Google limit is 50MB uncompressed)\n const sizeInMB = new Blob([content]).size / (1024 * 1024);\n if (sizeInMB > 50) {\n analysis.issues.push({\n id: `sitemap-too-large-size-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Sitemap exceeds size limit',\n description: `Sitemap is ${sizeInMB.toFixed(2)}MB. Maximum is 50MB.`,\n recommendation: 'Split the sitemap or compress it.',\n detectedAt: new Date().toISOString(),\n metadata: { sizeMB: sizeInMB },\n });\n }\n } catch (error) {\n consola.error('Failed to analyze sitemap:', error);\n analysis.issues.push({\n id: `sitemap-error-${hash(sitemapUrl)}`,\n url: sitemapUrl,\n category: 'technical',\n severity: 'error',\n title: 'Failed to parse sitemap',\n description: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n recommendation: 'Check sitemap validity using Google Search Console.',\n detectedAt: new Date().toISOString(),\n });\n }\n\n return analysis;\n}\n\n/**\n * Recursively analyze a sitemap and all its children\n */\nexport async function analyzeAllSitemaps(\n sitemapUrl: string,\n maxDepth = 3\n): Promise<SitemapAnalysis[]> {\n const results: SitemapAnalysis[] = [];\n const visited = new Set<string>();\n\n async function analyze(url: string, depth: number): Promise<void> {\n if (depth > maxDepth || visited.has(url)) return;\n visited.add(url);\n\n const analysis = await analyzeSitemap(url);\n results.push(analysis);\n\n // Recursively analyze child sitemaps\n for (const childUrl of analysis.childSitemaps) {\n await analyze(childUrl, depth + 1);\n }\n }\n\n await analyze(sitemapUrl, 0);\n return results;\n}\n\nfunction hash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return Math.abs(hash).toString(36);\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import pLimit from 'p-limit';
|
|
|
4
4
|
import pRetry from 'p-retry';
|
|
5
5
|
import { JWT } from 'google-auth-library';
|
|
6
6
|
import fs, { existsSync, readdirSync, rmSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'fs';
|
|
7
|
-
import {
|
|
7
|
+
import { parseHTML, DOMParser } from 'linkedom';
|
|
8
8
|
import robotsParser from 'robots-parser';
|
|
9
9
|
import path, { join, dirname } from 'path';
|
|
10
10
|
import { mkdir, writeFile } from 'fs/promises';
|
|
@@ -648,14 +648,16 @@ var SiteCrawler = class {
|
|
|
648
648
|
* Parse HTML and extract SEO-relevant data
|
|
649
649
|
*/
|
|
650
650
|
parseHtml(html, result, pageUrl, depth) {
|
|
651
|
-
const
|
|
652
|
-
|
|
651
|
+
const { document } = parseHTML(html);
|
|
652
|
+
const titleEl = document.querySelector("title");
|
|
653
|
+
result.title = titleEl?.textContent?.trim() || void 0;
|
|
653
654
|
if (!result.title) {
|
|
654
655
|
result.warnings.push("Missing title tag");
|
|
655
656
|
} else if (result.title.length > 60) {
|
|
656
657
|
result.warnings.push(`Title too long (${result.title.length} chars, recommended: <60)`);
|
|
657
658
|
}
|
|
658
|
-
|
|
659
|
+
const metaDesc = document.querySelector('meta[name="description"]');
|
|
660
|
+
result.metaDescription = metaDesc?.getAttribute("content")?.trim() || void 0;
|
|
659
661
|
if (!result.metaDescription) {
|
|
660
662
|
result.warnings.push("Missing meta description");
|
|
661
663
|
} else if (result.metaDescription.length > 160) {
|
|
@@ -663,25 +665,28 @@ var SiteCrawler = class {
|
|
|
663
665
|
`Meta description too long (${result.metaDescription.length} chars, recommended: <160)`
|
|
664
666
|
);
|
|
665
667
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
668
|
+
const metaRobots = document.querySelector('meta[name="robots"]');
|
|
669
|
+
result.metaRobots = metaRobots?.getAttribute("content")?.trim() || void 0;
|
|
670
|
+
const xRobots = document.querySelector('meta[http-equiv="X-Robots-Tag"]');
|
|
671
|
+
const xRobotsContent = xRobots?.getAttribute("content")?.trim();
|
|
672
|
+
if (xRobotsContent) {
|
|
673
|
+
result.metaRobots = result.metaRobots ? `${result.metaRobots}, ${xRobotsContent}` : xRobotsContent;
|
|
670
674
|
}
|
|
671
|
-
|
|
675
|
+
const canonical = document.querySelector('link[rel="canonical"]');
|
|
676
|
+
result.canonicalUrl = canonical?.getAttribute("href")?.trim() || void 0;
|
|
672
677
|
if (!result.canonicalUrl) {
|
|
673
678
|
result.warnings.push("Missing canonical tag");
|
|
674
679
|
}
|
|
675
|
-
result.h1 =
|
|
676
|
-
result.h2 =
|
|
680
|
+
result.h1 = Array.from(document.querySelectorAll("h1")).map((el) => el.textContent?.trim() || "");
|
|
681
|
+
result.h2 = Array.from(document.querySelectorAll("h2")).map((el) => el.textContent?.trim() || "");
|
|
677
682
|
if (result.h1.length === 0) {
|
|
678
683
|
result.warnings.push("Missing H1 tag");
|
|
679
684
|
} else if (result.h1.length > 1) {
|
|
680
685
|
result.warnings.push(`Multiple H1 tags (${result.h1.length})`);
|
|
681
686
|
}
|
|
682
|
-
|
|
683
|
-
const href =
|
|
684
|
-
if (!href)
|
|
687
|
+
for (const el of document.querySelectorAll("a[href]")) {
|
|
688
|
+
const href = el.getAttribute("href");
|
|
689
|
+
if (!href) continue;
|
|
685
690
|
try {
|
|
686
691
|
const linkUrl = new URL(href, pageUrl);
|
|
687
692
|
if (linkUrl.hostname === this.baseUrl.hostname) {
|
|
@@ -695,18 +700,19 @@ var SiteCrawler = class {
|
|
|
695
700
|
}
|
|
696
701
|
} catch {
|
|
697
702
|
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const src =
|
|
701
|
-
const alt =
|
|
703
|
+
}
|
|
704
|
+
for (const el of document.querySelectorAll("img")) {
|
|
705
|
+
const src = el.getAttribute("src");
|
|
706
|
+
const alt = el.getAttribute("alt");
|
|
707
|
+
const hasAltAttr = alt !== null;
|
|
702
708
|
if (src) {
|
|
703
709
|
result.images.push({
|
|
704
710
|
src,
|
|
705
|
-
alt,
|
|
706
|
-
hasAlt:
|
|
711
|
+
alt: alt ?? void 0,
|
|
712
|
+
hasAlt: hasAltAttr && alt.trim().length > 0
|
|
707
713
|
});
|
|
708
714
|
}
|
|
709
|
-
}
|
|
715
|
+
}
|
|
710
716
|
const imagesWithoutAlt = result.images.filter((img) => !img.hasAlt);
|
|
711
717
|
if (imagesWithoutAlt.length > 0) {
|
|
712
718
|
result.warnings.push(`${imagesWithoutAlt.length} images without alt text`);
|
|
@@ -1052,28 +1058,31 @@ async function analyzeSitemap(sitemapUrl) {
|
|
|
1052
1058
|
metadata: { contentType }
|
|
1053
1059
|
});
|
|
1054
1060
|
}
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1061
|
+
const parser = new DOMParser();
|
|
1062
|
+
const doc = parser.parseFromString(content, "text/xml");
|
|
1063
|
+
const sitemapIndex = doc.querySelector("sitemapindex");
|
|
1064
|
+
if (sitemapIndex) {
|
|
1058
1065
|
analysis.type = "sitemap-index";
|
|
1059
|
-
|
|
1060
|
-
const loc =
|
|
1066
|
+
for (const sitemap of doc.querySelectorAll("sitemap")) {
|
|
1067
|
+
const loc = sitemap.querySelector("loc")?.textContent?.trim();
|
|
1061
1068
|
if (loc) {
|
|
1062
1069
|
analysis.childSitemaps.push(loc);
|
|
1063
1070
|
}
|
|
1064
|
-
}
|
|
1071
|
+
}
|
|
1065
1072
|
consola2.debug(`Sitemap index contains ${analysis.childSitemaps.length} sitemaps`);
|
|
1066
1073
|
} else {
|
|
1067
1074
|
analysis.type = "sitemap";
|
|
1068
|
-
|
|
1069
|
-
const loc =
|
|
1075
|
+
for (const url of doc.querySelectorAll("url")) {
|
|
1076
|
+
const loc = url.querySelector("loc")?.textContent?.trim();
|
|
1070
1077
|
if (loc) {
|
|
1071
1078
|
analysis.urls.push(loc);
|
|
1072
1079
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1080
|
+
if (!analysis.lastmod) {
|
|
1081
|
+
const lastmod = url.querySelector("lastmod")?.textContent?.trim();
|
|
1082
|
+
if (lastmod) {
|
|
1083
|
+
analysis.lastmod = lastmod;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1077
1086
|
}
|
|
1078
1087
|
consola2.debug(`Sitemap contains ${analysis.urls.length} URLs`);
|
|
1079
1088
|
}
|