@rankcli/agent-runtime 0.0.15 → 0.0.16

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 (202) hide show
  1. package/dist/index.d.mts +43 -393
  2. package/dist/index.d.ts +43 -393
  3. package/dist/index.js +306 -6526
  4. package/dist/index.mjs +202 -6412
  5. package/package.json +6 -1
  6. package/scripts/build-deno.ts +0 -134
  7. package/src/analyzers/core-web-vitals-analyzer.test.ts +0 -236
  8. package/src/analyzers/core-web-vitals-analyzer.ts +0 -557
  9. package/src/analyzers/geo-analyzer.test.ts +0 -310
  10. package/src/analyzers/geo-analyzer.ts +0 -814
  11. package/src/analyzers/image-optimization-analyzer.test.ts +0 -145
  12. package/src/analyzers/image-optimization-analyzer.ts +0 -348
  13. package/src/analyzers/index.ts +0 -233
  14. package/src/analyzers/internal-linking-analyzer.test.ts +0 -141
  15. package/src/analyzers/internal-linking-analyzer.ts +0 -419
  16. package/src/analyzers/mobile-seo-analyzer.test.ts +0 -140
  17. package/src/analyzers/mobile-seo-analyzer.ts +0 -455
  18. package/src/analyzers/security-headers-analyzer.test.ts +0 -115
  19. package/src/analyzers/security-headers-analyzer.ts +0 -318
  20. package/src/analyzers/structured-data-analyzer.test.ts +0 -210
  21. package/src/analyzers/structured-data-analyzer.ts +0 -590
  22. package/src/audit/ai/analyzer.ts +0 -347
  23. package/src/audit/ai/index.ts +0 -29
  24. package/src/audit/ai/prompts/content-analysis.ts +0 -271
  25. package/src/audit/ai/types.ts +0 -179
  26. package/src/audit/checks/additional-checks.ts +0 -439
  27. package/src/audit/checks/agent-experience.ts +0 -108
  28. package/src/audit/checks/ai-citation-worthiness.ts +0 -399
  29. package/src/audit/checks/ai-content-structure.ts +0 -325
  30. package/src/audit/checks/ai-readiness.ts +0 -406
  31. package/src/audit/checks/anchor-text.ts +0 -179
  32. package/src/audit/checks/answer-conciseness.ts +0 -322
  33. package/src/audit/checks/asset-minification.ts +0 -270
  34. package/src/audit/checks/bing-optimization.ts +0 -206
  35. package/src/audit/checks/brand-mention-optimization.ts +0 -349
  36. package/src/audit/checks/caching-headers.ts +0 -305
  37. package/src/audit/checks/canonical-advanced.ts +0 -150
  38. package/src/audit/checks/canonical-domain.ts +0 -196
  39. package/src/audit/checks/citation-quality.ts +0 -358
  40. package/src/audit/checks/client-rendering.ts +0 -542
  41. package/src/audit/checks/color-contrast.ts +0 -342
  42. package/src/audit/checks/content-freshness.ts +0 -170
  43. package/src/audit/checks/content-science.ts +0 -589
  44. package/src/audit/checks/conversion-elements.ts +0 -526
  45. package/src/audit/checks/crawlability.ts +0 -220
  46. package/src/audit/checks/directory-listing.ts +0 -172
  47. package/src/audit/checks/dom-analysis.ts +0 -191
  48. package/src/audit/checks/dom-size.ts +0 -246
  49. package/src/audit/checks/duplicate-content.ts +0 -194
  50. package/src/audit/checks/eeat-signals.ts +0 -990
  51. package/src/audit/checks/entity-seo.ts +0 -396
  52. package/src/audit/checks/featured-snippet.ts +0 -473
  53. package/src/audit/checks/freshness-signals.ts +0 -443
  54. package/src/audit/checks/funnel-intent.ts +0 -463
  55. package/src/audit/checks/hreflang.ts +0 -174
  56. package/src/audit/checks/html-compliance.ts +0 -302
  57. package/src/audit/checks/image-dimensions.ts +0 -167
  58. package/src/audit/checks/images.ts +0 -160
  59. package/src/audit/checks/indexnow.ts +0 -275
  60. package/src/audit/checks/interactive-tools.ts +0 -475
  61. package/src/audit/checks/internal-link-graph.ts +0 -436
  62. package/src/audit/checks/keyword-analysis.ts +0 -239
  63. package/src/audit/checks/keyword-cannibalization.ts +0 -385
  64. package/src/audit/checks/keyword-placement.ts +0 -471
  65. package/src/audit/checks/links.ts +0 -273
  66. package/src/audit/checks/llms-txt.ts +0 -224
  67. package/src/audit/checks/local-seo.ts +0 -296
  68. package/src/audit/checks/mobile.ts +0 -167
  69. package/src/audit/checks/modern-images.ts +0 -226
  70. package/src/audit/checks/navboost-signals.ts +0 -395
  71. package/src/audit/checks/on-page.ts +0 -209
  72. package/src/audit/checks/page-resources.ts +0 -285
  73. package/src/audit/checks/pagination.ts +0 -180
  74. package/src/audit/checks/performance.ts +0 -153
  75. package/src/audit/checks/platform-presence.ts +0 -580
  76. package/src/audit/checks/rag-chunk-readiness.test.ts +0 -159
  77. package/src/audit/checks/rag-chunk-readiness.ts +0 -163
  78. package/src/audit/checks/redirect-analysis.ts +0 -153
  79. package/src/audit/checks/redirect-chain.ts +0 -389
  80. package/src/audit/checks/resource-hints.ts +0 -420
  81. package/src/audit/checks/responsive-css.ts +0 -247
  82. package/src/audit/checks/responsive-images.ts +0 -396
  83. package/src/audit/checks/review-ecosystem.ts +0 -415
  84. package/src/audit/checks/robots-validation.ts +0 -373
  85. package/src/audit/checks/security-headers.ts +0 -188
  86. package/src/audit/checks/security.ts +0 -144
  87. package/src/audit/checks/serp-preview.ts +0 -251
  88. package/src/audit/checks/site-maturity.ts +0 -453
  89. package/src/audit/checks/social-meta.test.ts +0 -275
  90. package/src/audit/checks/social-meta.ts +0 -134
  91. package/src/audit/checks/soft-404.ts +0 -151
  92. package/src/audit/checks/structured-data.ts +0 -238
  93. package/src/audit/checks/tech-detection.ts +0 -496
  94. package/src/audit/checks/topical-clusters.ts +0 -435
  95. package/src/audit/checks/tracker-bloat.ts +0 -462
  96. package/src/audit/checks/tracking-verification.test.ts +0 -371
  97. package/src/audit/checks/tracking-verification.ts +0 -636
  98. package/src/audit/checks/url-safety.ts +0 -682
  99. package/src/audit/deno-entry.ts +0 -66
  100. package/src/audit/discovery/index.ts +0 -15
  101. package/src/audit/discovery/link-crawler.ts +0 -233
  102. package/src/audit/discovery/repo-routes.ts +0 -347
  103. package/src/audit/engine.ts +0 -633
  104. package/src/audit/fixes/index.ts +0 -209
  105. package/src/audit/fixes/social-meta-fixes.test.ts +0 -329
  106. package/src/audit/fixes/social-meta-fixes.ts +0 -463
  107. package/src/audit/index.ts +0 -74
  108. package/src/audit/runner.test.ts +0 -299
  109. package/src/audit/runner.ts +0 -130
  110. package/src/audit/types.ts +0 -1987
  111. package/src/content/featured-snippet.ts +0 -367
  112. package/src/content/generator.test.ts +0 -534
  113. package/src/content/generator.ts +0 -501
  114. package/src/content/headline.ts +0 -317
  115. package/src/content/index.ts +0 -62
  116. package/src/content/intent.ts +0 -258
  117. package/src/content/keyword-density.ts +0 -349
  118. package/src/content/readability.ts +0 -262
  119. package/src/executor.ts +0 -336
  120. package/src/fixer/framework-fixes.test.ts +0 -489
  121. package/src/fixer/framework-fixes.ts +0 -5287
  122. package/src/fixer/index.ts +0 -6
  123. package/src/fixer/schemas.ts +0 -971
  124. package/src/fixer.ts +0 -642
  125. package/src/frameworks/detector.test.ts +0 -248
  126. package/src/frameworks/detector.ts +0 -899
  127. package/src/frameworks/index.ts +0 -68
  128. package/src/frameworks/recipes/angular.yaml +0 -171
  129. package/src/frameworks/recipes/astro.yaml +0 -206
  130. package/src/frameworks/recipes/django.yaml +0 -180
  131. package/src/frameworks/recipes/laravel.yaml +0 -137
  132. package/src/frameworks/recipes/nextjs.yaml +0 -268
  133. package/src/frameworks/recipes/nuxt.yaml +0 -175
  134. package/src/frameworks/recipes/rails.yaml +0 -188
  135. package/src/frameworks/recipes/react.yaml +0 -202
  136. package/src/frameworks/recipes/sveltekit.yaml +0 -154
  137. package/src/frameworks/recipes/vue.yaml +0 -137
  138. package/src/frameworks/recipes/wordpress.yaml +0 -209
  139. package/src/frameworks/suggestion-engine.ts +0 -357
  140. package/src/geo/geo-content.test.ts +0 -305
  141. package/src/geo/geo-content.ts +0 -266
  142. package/src/geo/geo-history.test.ts +0 -473
  143. package/src/geo/geo-history.ts +0 -433
  144. package/src/geo/geo-tracker.test.ts +0 -359
  145. package/src/geo/geo-tracker.ts +0 -411
  146. package/src/geo/index.ts +0 -11
  147. package/src/geo/llm-citation-checker.ts +0 -188
  148. package/src/git/commit-helper.test.ts +0 -261
  149. package/src/git/commit-helper.ts +0 -329
  150. package/src/git/index.ts +0 -12
  151. package/src/git/pr-helper.test.ts +0 -284
  152. package/src/git/pr-helper.ts +0 -307
  153. package/src/index.ts +0 -76
  154. package/src/keywords/ai-keyword-engine.ts +0 -1062
  155. package/src/keywords/ai-summarizer.ts +0 -387
  156. package/src/keywords/ci-mode.ts +0 -555
  157. package/src/keywords/engine.ts +0 -359
  158. package/src/keywords/index.ts +0 -151
  159. package/src/keywords/llm-judge.ts +0 -357
  160. package/src/keywords/nlp-analysis.ts +0 -706
  161. package/src/keywords/prioritizer.ts +0 -295
  162. package/src/keywords/site-crawler.ts +0 -342
  163. package/src/keywords/sources/autocomplete.ts +0 -139
  164. package/src/keywords/sources/competitive-search.ts +0 -450
  165. package/src/keywords/sources/competitor-analysis.ts +0 -374
  166. package/src/keywords/sources/dataforseo.ts +0 -206
  167. package/src/keywords/sources/free-sources.ts +0 -294
  168. package/src/keywords/sources/gsc.ts +0 -123
  169. package/src/keywords/topic-grouping.ts +0 -327
  170. package/src/keywords/types.ts +0 -144
  171. package/src/keywords/wizard.ts +0 -457
  172. package/src/loader.ts +0 -40
  173. package/src/ranking/index.ts +0 -5
  174. package/src/ranking/serp-client.ts +0 -348
  175. package/src/ranking/tracker.ts +0 -380
  176. package/src/ranking/types.ts +0 -123
  177. package/src/reports/index.ts +0 -7
  178. package/src/reports/report-generator.test.ts +0 -293
  179. package/src/reports/report-generator.ts +0 -713
  180. package/src/scheduler/alerts.test.ts +0 -458
  181. package/src/scheduler/alerts.ts +0 -328
  182. package/src/scheduler/index.ts +0 -8
  183. package/src/scheduler/scheduled-audit.test.ts +0 -377
  184. package/src/scheduler/scheduled-audit.ts +0 -149
  185. package/src/test/integration-test.ts +0 -325
  186. package/src/tools/analyzer.ts +0 -373
  187. package/src/tools/crawl.ts +0 -293
  188. package/src/tools/files.ts +0 -301
  189. package/src/tools/h1-fixer.ts +0 -249
  190. package/src/tools/index.ts +0 -67
  191. package/src/tracking/github-action.ts +0 -326
  192. package/src/tracking/google-analytics.ts +0 -265
  193. package/src/tracking/index.ts +0 -45
  194. package/src/tracking/report-generator.ts +0 -386
  195. package/src/tracking/search-console.ts +0 -335
  196. package/src/types.ts +0 -148
  197. package/src/utils/http.ts +0 -302
  198. package/src/wasm-adapter.ts +0 -297
  199. package/src/wasm-entry.ts +0 -14
  200. package/tsconfig.json +0 -17
  201. package/tsup.wasm.config.ts +0 -26
  202. package/vitest.config.ts +0 -15
@@ -1,633 +0,0 @@
1
- import { httpGet } from '../utils/http.js';
2
- import type { AuditIssue, AuditReport, HealthScore, PageAudit } from './types.js';
3
- import { runCrawlabilityChecks } from './checks/crawlability.js';
4
- import { analyzeOnPage } from './checks/on-page.js';
5
- import { analyzeLinks } from './checks/links.js';
6
- import { analyzeImages } from './checks/images.js';
7
- import { analyzePerformance, fetchCoreWebVitals } from './checks/performance.js';
8
- import { analyzeSecurity, checkCertificate } from './checks/security.js';
9
- import { analyzeStructuredData, suggestSchemaTypes } from './checks/structured-data.js';
10
- import { analyzeMobile, checkMobileResources } from './checks/mobile.js';
11
- // Advanced checks
12
- import { analyzeHreflang } from './checks/hreflang.js';
13
- import { analyzeSocialMeta } from './checks/social-meta.js';
14
- import { detectSoft404 } from './checks/soft-404.js';
15
- import { analyzeAnchorText } from './checks/anchor-text.js';
16
- import { analyzeCanonicalAdvanced } from './checks/canonical-advanced.js';
17
- import { analyzePagination } from './checks/pagination.js';
18
- import { analyzeRedirects } from './checks/redirect-analysis.js';
19
- // New competitor-parity checks
20
- import { runAIReadinessChecks } from './checks/ai-readiness.js';
21
- import { analyzeSERPPreview } from './checks/serp-preview.js';
22
- import { analyzeLocalSEO } from './checks/local-seo.js';
23
- import { analyzeSecurityHeaders } from './checks/security-headers.js';
24
- import { analyzeContentFreshness } from './checks/content-freshness.js';
25
- import { analyzeDOMStructure } from './checks/dom-analysis.js';
26
- import { analyzeModernImages } from './checks/modern-images.js';
27
- import { detectTechnologies } from './checks/tech-detection.js';
28
- import { analyzeKeywords } from './checks/keyword-analysis.js';
29
- import { runAdditionalChecks } from './checks/additional-checks.js';
30
- // Advanced checks from SEO research
31
- import { analyzeFeaturedSnippet } from './checks/featured-snippet.js';
32
- import { analyzeInternalLinkGraph } from './checks/internal-link-graph.js';
33
- import { analyzeResourceHints } from './checks/resource-hints.js';
34
- import { analyzeEEATSignals } from './checks/eeat-signals.js';
35
- import { analyzeIndexNow } from './checks/indexnow.js';
36
- import { analyzeContentScience } from './checks/content-science.js';
37
- import { analyzeSiteMaturity } from './checks/site-maturity.js';
38
- import { analyzeKeywordCannibalizationSinglePage } from './checks/keyword-cannibalization.js';
39
- // New checks from Technical SEO for Developers & Best SEO Strategies 2025
40
- import { analyzeTrackerBloat } from './checks/tracker-bloat.js';
41
- import { analyzeClientRendering } from './checks/client-rendering.js';
42
- import { analyzeRedirectChain } from './checks/redirect-chain.js';
43
- import { analyzeResponsiveImages } from './checks/responsive-images.js';
44
- // New checks from 2026 SEO transcripts (Nathan Gotch & Neil Patel)
45
- import { analyzeConversionElements } from './checks/conversion-elements.js';
46
- import { analyzeKeywordPlacement } from './checks/keyword-placement.js';
47
- import { analyzeTopicalClusters } from './checks/topical-clusters.js';
48
- import { analyzePlatformPresence } from './checks/platform-presence.js';
49
- // Advanced SEO Tips 2026 & React SEO
50
- import { analyzeInteractiveTools } from './checks/interactive-tools.js';
51
- import { analyzeFunnelIntent } from './checks/funnel-intent.js';
52
- // Cutting-edge differentiating checks (Google leak, Entity SEO, QDF)
53
- import { analyzeNavBoostSignals } from './checks/navboost-signals.js';
54
- import { analyzeEntitySEO } from './checks/entity-seo.js';
55
- import { analyzeFreshnessSignals } from './checks/freshness-signals.js';
56
- // AI Search Optimization checks (2026 - ChatGPT, AI Overviews)
57
- import { analyzeBingOptimization } from './checks/bing-optimization.js';
58
- import { analyzeAIContentStructure } from './checks/ai-content-structure.js';
59
- import { analyzeRAGChunkReadiness } from './checks/rag-chunk-readiness.js';
60
- import { analyzeCitationQuality } from './checks/citation-quality.js';
61
- import { analyzeAnswerConciseness } from './checks/answer-conciseness.js';
62
- // AI SEO Skills checks (brand narrative, citation worthiness, review ecosystem)
63
- import { analyzeBrandMentionOptimization } from './checks/brand-mention-optimization.js';
64
- import { analyzeAICitationWorthiness } from './checks/ai-citation-worthiness.js';
65
- import { analyzeReviewEcosystem } from './checks/review-ecosystem.js';
66
- // Competitor-parity checks (from missing-checks.md analysis)
67
- import { validateRobotsTxt } from './checks/robots-validation.js';
68
- import { analyzeLlmsTxt } from './checks/llms-txt.js';
69
- import { analyzeHtmlCompliance } from './checks/html-compliance.js';
70
- import { analyzeCanonicalDomain } from './checks/canonical-domain.js';
71
- import { analyzeCachingHeaders } from './checks/caching-headers.js';
72
- import { analyzeDomSize } from './checks/dom-size.js';
73
- import { analyzeImageDimensions } from './checks/image-dimensions.js';
74
- import { analyzeColorContrast } from './checks/color-contrast.js';
75
- // New checks from Rank Math analysis
76
- import { analyzeAssetMinification } from './checks/asset-minification.js';
77
- import { analyzePageResources } from './checks/page-resources.js';
78
- import { analyzeResponsiveCss } from './checks/responsive-css.js';
79
- import { analyzeDirectoryListing } from './checks/directory-listing.js';
80
- import { analyzeUrlSafety } from './checks/url-safety.js';
81
- import { analyzeTrackingVerification } from './checks/tracking-verification.js';
82
-
83
- export interface AuditOptions {
84
- url: string;
85
- checkBrokenLinks?: boolean;
86
- checkHreflangUrls?: boolean;
87
- checkCanonicalChain?: boolean;
88
- maxPages?: number;
89
- cruxApiKey?: string;
90
- /**
91
- * Maximum number of checks to run. Used for tier enforcement:
92
- * - 50: Free unregistered users (core checks only)
93
- * - 100: Free registered users (core + some premium checks)
94
- * - 280+: Paid users (all checks)
95
- */
96
- checksLimit?: number;
97
- }
98
-
99
- export async function runFullAudit(options: AuditOptions): Promise<AuditReport> {
100
- const { url, checkBrokenLinks = false, checkHreflangUrls = false, checkCanonicalChain = false, cruxApiKey, checksLimit = 280 } = options;
101
- const allIssues: AuditIssue[] = [];
102
- const pages: PageAudit[] = [];
103
-
104
- // Determine tier based on checksLimit
105
- // Free unregistered: 50, Free registered: 100, Paid: 280+
106
- const tier = checksLimit <= 50 ? 'free' : checksLimit <= 100 ? 'registered' : 'paid';
107
- const runPremiumChecks = tier === 'paid';
108
- const runExtendedChecks = tier !== 'free'; // registered + paid
109
-
110
- const parsedUrl = new URL(url);
111
- const domain = parsedUrl.hostname;
112
-
113
- // Progress messages go to stderr, not stdout: stdout is reserved for the
114
- // audit's actual JSON output (`rankcli audit --output json > file.json`),
115
- // and printing progress via console.log here corrupts that redirect.
116
- console.error(`\n🔍 Running comprehensive SEO audit on ${url}...\n`);
117
-
118
- // ========== PHASE 1: CRAWLABILITY + FETCH (PARALLEL) ==========
119
- console.error('📋 Phase 1: Crawlability checks + page fetch (parallel)...');
120
-
121
- const [crawlabilityResult, fetchResult] = await Promise.all([
122
- runCrawlabilityChecks(url).catch(err => {
123
- console.error('Crawlability check failed:', err);
124
- return [] as AuditIssue[];
125
- }),
126
- httpGet<string>(url, {
127
- timeout: 30000,
128
- validateStatus: () => true,
129
- }).catch(err => {
130
- console.error('Main page fetch failed:', err instanceof Error ? { message: err.message, cause: (err as NodeJS.ErrnoException).cause } : err);
131
- return { error: err, data: '', headers: {} as Record<string, string> };
132
- }),
133
- ]);
134
-
135
- allIssues.push(...crawlabilityResult);
136
-
137
- // Check if fetch failed
138
- if ('error' in fetchResult) {
139
- const err = fetchResult.error;
140
- const errorMsg = err instanceof Error ? err.message : 'Unknown error';
141
- const errorCause = err instanceof Error && (err as NodeJS.ErrnoException).cause
142
- ? ` (${String((err as NodeJS.ErrnoException).cause)})`
143
- : '';
144
- allIssues.push({
145
- code: 'FETCH_ERROR',
146
- severity: 'error',
147
- category: 'indexability', // Not crawlability - robots/sitemap checks may have succeeded
148
- title: 'Page fetch failed',
149
- description: `Could not load page content: ${errorMsg}${errorCause}. Only robots.txt and sitemap checks were performed.`,
150
- impact: 'Cannot perform full SEO audit without page HTML content.',
151
- howToFix: 'Verify the URL is accessible, the server is responding, and there are no firewall/geo blocks.',
152
- affectedUrls: [url],
153
- details: { error: errorMsg, cause: errorCause || undefined },
154
- });
155
- return createReport(url, domain, allIssues, pages);
156
- }
157
-
158
- const html = fetchResult.data;
159
- const headers = fetchResult.headers;
160
-
161
- // ========== PHASE 2: SYNCHRONOUS HTML CHECKS (PARALLEL) ==========
162
- console.error(`📝 Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
163
-
164
- // ===== CORE CHECKS (all tiers) =====
165
- const onPageResult = analyzeOnPage(html, url);
166
- const structuredDataResult = analyzeStructuredData(html, url);
167
- const mobileResult = analyzeMobile(html, url);
168
- const mobileResourceIssues = checkMobileResources(html, url);
169
- const socialResult = analyzeSocialMeta(html, url);
170
- const soft404Result = detectSoft404(html, url, 200);
171
- const serpResult = analyzeSERPPreview(html, url);
172
- const domResult = analyzeDOMStructure(html, url);
173
- const techResult = detectTechnologies(html, url, headers);
174
- const keywordResult = analyzeKeywords(html, url);
175
- // Pagination depends on onPageData.canonical
176
- const paginationResult = analyzePagination(html, url, onPageResult.data.canonical);
177
- // URL Safety check (local hash database - like Google Safe Browsing)
178
- const urlSafetyResult = analyzeUrlSafety(url);
179
- // Competitor-parity checks (sync) - important for all users
180
- const domSizeResult = analyzeDomSize(html, url);
181
- const imageDimensionsResult = analyzeImageDimensions(html, url);
182
- // Tracking & verification checks (GA4, GSC, Bing, Schema.org)
183
- const trackingResult = analyzeTrackingVerification(html, url);
184
-
185
- // ===== EXTENDED CHECKS (registered + paid tiers) =====
186
- const anchorResult = runExtendedChecks ? analyzeAnchorText(html, url) : { issues: [], data: {} };
187
- const localSeoResult = runExtendedChecks ? analyzeLocalSEO(html, url) : { issues: [], data: {} };
188
- const modernImageResult = runExtendedChecks ? analyzeModernImages(html, url) : { issues: [], data: {} };
189
- const resourceHintResult = runExtendedChecks ? analyzeResourceHints(html, url) : { issues: [], data: {} };
190
- const trackerResult = runExtendedChecks ? analyzeTrackerBloat(html, url) : { issues: [], data: {} };
191
- const csrResult = runExtendedChecks ? analyzeClientRendering(html, url) : { issues: [], data: {} };
192
- const responsiveImgResult = runExtendedChecks ? analyzeResponsiveImages(html, url) : { issues: [], data: {} };
193
- const colorContrastResult = runExtendedChecks ? analyzeColorContrast(html, url) : { issues: [], data: {} };
194
- const pageResourcesResult = runExtendedChecks ? analyzePageResources(html, url) : { issues: [], data: {} };
195
-
196
- // ===== PREMIUM CHECKS (paid tier only) =====
197
- const snippetResult = runPremiumChecks ? analyzeFeaturedSnippet(html, url) : { issues: [], data: {} };
198
- const linkGraphResult = runPremiumChecks ? analyzeInternalLinkGraph(html, url) : { issues: [], data: {} };
199
- const eeatResult = runPremiumChecks ? analyzeEEATSignals(html, url) : { issues: [], data: {} };
200
- const contentScienceResult = runPremiumChecks ? analyzeContentScience(html, url) : { issues: [], data: {} };
201
- const cannibalizationResult = runPremiumChecks ? analyzeKeywordCannibalizationSinglePage(html, url) : { issues: [], data: {} };
202
- const conversionResult = runPremiumChecks ? analyzeConversionElements(html, url) : { issues: [], data: {} };
203
- const keywordPlacementResult = runPremiumChecks ? analyzeKeywordPlacement(html, url) : { issues: [], data: {} };
204
- const clusterResult = runPremiumChecks ? analyzeTopicalClusters(html, url) : { issues: [], data: {} };
205
- const platformResult = runPremiumChecks ? analyzePlatformPresence(html, url) : { issues: [], data: {} };
206
- const toolResult = runPremiumChecks ? analyzeInteractiveTools(html, url) : { issues: [], data: {} };
207
- const funnelResult = runPremiumChecks ? analyzeFunnelIntent(html, url) : { issues: [], data: {} };
208
- const navboostResult = runPremiumChecks ? analyzeNavBoostSignals(html, url) : { issues: [], data: {} };
209
- const entityResult = runPremiumChecks ? analyzeEntitySEO(html, url) : { issues: [], data: {} };
210
- const qdfFreshnessResult = runPremiumChecks ? analyzeFreshnessSignals(html, url) : { issues: [], data: {} };
211
- // AI Search Optimization (2026) - Premium only
212
- const aiContentStructureResult = runPremiumChecks ? analyzeAIContentStructure(html, url) : { issues: [], data: {} };
213
- const ragChunkReadinessResult = runPremiumChecks ? analyzeRAGChunkReadiness(html, url) : { issues: [], data: {} };
214
- const citationQualityResult = runPremiumChecks ? analyzeCitationQuality(html, url) : { issues: [], data: {} };
215
- const answerConcisenessResult = runPremiumChecks ? analyzeAnswerConciseness(html, url) : { issues: [], data: {} };
216
- // AI SEO Skills - Premium only
217
- const brandMentionResult = runPremiumChecks ? analyzeBrandMentionOptimization(html, url) : { issues: [], data: {} };
218
- const citationWorthinessResult = runPremiumChecks ? analyzeAICitationWorthiness(html, url) : { issues: [], data: {} };
219
- const reviewEcosystemResult = runPremiumChecks ? analyzeReviewEcosystem(html, url) : { issues: [], data: {} };
220
-
221
- // Collect all sync check issues
222
- allIssues.push(
223
- ...onPageResult.issues,
224
- ...structuredDataResult.issues,
225
- ...mobileResult.issues,
226
- ...mobileResourceIssues,
227
- ...socialResult.issues,
228
- ...soft404Result.issues,
229
- ...anchorResult.issues,
230
- ...serpResult.issues,
231
- ...localSeoResult.issues,
232
- ...domResult.issues,
233
- ...modernImageResult.issues,
234
- ...techResult.issues,
235
- ...keywordResult.issues,
236
- ...snippetResult.issues,
237
- ...linkGraphResult.issues,
238
- ...resourceHintResult.issues,
239
- ...eeatResult.issues,
240
- ...contentScienceResult.issues,
241
- ...cannibalizationResult.issues,
242
- ...trackerResult.issues,
243
- ...csrResult.issues,
244
- ...responsiveImgResult.issues,
245
- ...conversionResult.issues,
246
- ...keywordPlacementResult.issues,
247
- ...clusterResult.issues,
248
- ...platformResult.issues,
249
- ...toolResult.issues,
250
- ...funnelResult.issues,
251
- ...navboostResult.issues,
252
- ...entityResult.issues,
253
- ...qdfFreshnessResult.issues,
254
- ...aiContentStructureResult.issues,
255
- ...ragChunkReadinessResult.issues,
256
- ...citationQualityResult.issues,
257
- ...answerConcisenessResult.issues,
258
- ...brandMentionResult.issues,
259
- ...citationWorthinessResult.issues,
260
- ...reviewEcosystemResult.issues,
261
- ...domSizeResult.issues,
262
- ...imageDimensionsResult.issues,
263
- ...colorContrastResult.issues,
264
- ...pageResourcesResult.issues,
265
- ...urlSafetyResult.issues,
266
- ...paginationResult.issues,
267
- ...trackingResult.issues,
268
- );
269
-
270
- // Suggest schema types if none found
271
- if (!structuredDataResult.data.hasSchema) {
272
- const suggestions = suggestSchemaTypes(html, url);
273
- if (suggestions.length > 0 && structuredDataResult.issues.length > 0) {
274
- const lastSchemaIssue = structuredDataResult.issues[structuredDataResult.issues.length - 1];
275
- lastSchemaIssue.details = {
276
- ...lastSchemaIssue.details,
277
- suggestedTypes: suggestions,
278
- };
279
- }
280
- }
281
-
282
- // ========== PHASE 3: ASYNC CHECKS (PARALLEL) ==========
283
- console.error('🔗 Phase 3: Running async checks (parallel)...');
284
-
285
- // Helper to safely run async checks with error handling and timeout
286
- const safeAsync = async <T>(
287
- name: string,
288
- fn: () => Promise<{ issues: AuditIssue[]; data: T }>,
289
- timeoutMs: number = 10000, // 10 second default timeout per check
290
- ): Promise<AuditIssue[]> => {
291
- try {
292
- const resultPromise = fn();
293
- const timeoutPromise = new Promise<never>((_, reject) =>
294
- setTimeout(() => reject(new Error(`${name} timed out`)), timeoutMs)
295
- );
296
- const result = await Promise.race([resultPromise, timeoutPromise]);
297
- return result.issues;
298
- } catch (err) {
299
- console.error(`${name} failed:`, err);
300
- return [];
301
- }
302
- };
303
-
304
- // Helper for async checks that only return issues
305
- const safeAsyncIssues = async (
306
- name: string,
307
- fn: () => Promise<AuditIssue[]>,
308
- timeoutMs: number = 10000,
309
- ): Promise<AuditIssue[]> => {
310
- try {
311
- const resultPromise = fn();
312
- const timeoutPromise = new Promise<never>((_, reject) =>
313
- setTimeout(() => reject(new Error(`${name} timed out`)), timeoutMs)
314
- );
315
- return await Promise.race([resultPromise, timeoutPromise]);
316
- } catch (err) {
317
- console.error(`${name} failed:`, err);
318
- return [];
319
- }
320
- };
321
-
322
- // Run performance separately to capture data for page audit
323
- let perfData = { loadTime: 0 };
324
- const performancePromise = analyzePerformance(url)
325
- .then(result => {
326
- perfData = result.data;
327
- return result.issues;
328
- })
329
- .catch(err => {
330
- console.error('Performance check failed:', err);
331
- return [] as AuditIssue[];
332
- });
333
-
334
- // Run all async checks in parallel (respecting tier limits)
335
- const asyncChecks: Promise<AuditIssue[]>[] = [];
336
-
337
- // ===== CORE ASYNC CHECKS (all tiers) =====
338
- asyncChecks.push(safeAsync('Links', () => analyzeLinks(html, url, checkBrokenLinks)));
339
- asyncChecks.push(safeAsync('Images', () => analyzeImages(html, url, checkBrokenLinks)));
340
- asyncChecks.push(performancePromise);
341
- asyncChecks.push(safeAsync('Security', () => analyzeSecurity(html, url, headers)));
342
- asyncChecks.push(safeAsyncIssues('Certificate', async () => (await checkCertificate(url)).issues));
343
- asyncChecks.push(safeAsync('Redirects', () => analyzeRedirects(url)));
344
- asyncChecks.push(safeAsync('Robots Validation', () => validateRobotsTxt(url)));
345
- asyncChecks.push(safeAsync('Canonical Domain', () => analyzeCanonicalDomain(url)));
346
-
347
- // ===== EXTENDED ASYNC CHECKS (registered + paid tiers) =====
348
- if (runExtendedChecks) {
349
- asyncChecks.push(safeAsync('Hreflang', () => analyzeHreflang(html, url, { validateUrls: checkHreflangUrls })));
350
- asyncChecks.push(safeAsync('Canonical Advanced', () => analyzeCanonicalAdvanced(html, url, { checkChain: checkCanonicalChain })));
351
- asyncChecks.push(safeAsync('Security Headers', () => analyzeSecurityHeaders(url)));
352
- asyncChecks.push(safeAsync('Caching Headers', () => analyzeCachingHeaders(url, headers)));
353
- asyncChecks.push(safeAsync('HTML Compliance', () => analyzeHtmlCompliance(html, url, headers)));
354
- asyncChecks.push(safeAsync('Redirect Chain', () => analyzeRedirectChain(url)));
355
- asyncChecks.push(safeAsync('Asset Minification', () => analyzeAssetMinification(html, url)));
356
- asyncChecks.push(safeAsync('Responsive CSS', () => analyzeResponsiveCss(html, url)));
357
- asyncChecks.push(safeAsync('Directory Listing', () => analyzeDirectoryListing(url)));
358
- }
359
-
360
- // ===== PREMIUM ASYNC CHECKS (paid tier only) =====
361
- if (runPremiumChecks) {
362
- asyncChecks.push(safeAsync('AI Readiness', () => runAIReadinessChecks(url, html)));
363
- asyncChecks.push(safeAsync('Content Freshness', () => analyzeContentFreshness(url, html)));
364
- asyncChecks.push(safeAsync('Additional Checks', () => runAdditionalChecks(url, html)));
365
- asyncChecks.push(safeAsync('IndexNow', () => analyzeIndexNow(html, url, headers)));
366
- asyncChecks.push(safeAsync('Site Maturity', () => analyzeSiteMaturity(html, url)));
367
- asyncChecks.push(safeAsync('Bing Optimization', () => analyzeBingOptimization(html, url)));
368
- asyncChecks.push(safeAsync('LLMs.txt', () => analyzeLlmsTxt(url)));
369
- // Core Web Vitals (optional, premium only)
370
- if (cruxApiKey) {
371
- asyncChecks.push(safeAsyncIssues('Core Web Vitals', async () => {
372
- const cwv = await fetchCoreWebVitals(url, cruxApiKey);
373
- const issues: AuditIssue[] = [];
374
- if (cwv) {
375
- if (cwv.lcp?.rating === 'poor') {
376
- issues.push({
377
- code: 'LCP_POOR',
378
- severity: 'error',
379
- category: 'performance',
380
- title: 'Poor Largest Contentful Paint (LCP)',
381
- description: `LCP is ${cwv.lcp.value}ms (should be under 2500ms)`,
382
- impact: 'Direct Core Web Vitals ranking factor.',
383
- howToFix: 'Optimize hero images, preload critical resources, improve server response time.',
384
- affectedUrls: [url],
385
- details: { lcp: cwv.lcp },
386
- });
387
- }
388
- if (cwv.cls?.rating === 'poor') {
389
- issues.push({
390
- code: 'CLS_POOR',
391
- severity: 'error',
392
- category: 'performance',
393
- title: 'Poor Cumulative Layout Shift (CLS)',
394
- description: `CLS is ${cwv.cls.value} (should be under 0.1)`,
395
- impact: 'Direct Core Web Vitals ranking factor.',
396
- howToFix: 'Set image dimensions, avoid inserting content above existing content.',
397
- affectedUrls: [url],
398
- details: { cls: cwv.cls },
399
- });
400
- }
401
- }
402
- return issues;
403
- }));
404
- }
405
- }
406
-
407
- const asyncResults = await Promise.all(asyncChecks);
408
-
409
- // Collect all async issues
410
- for (const issues of asyncResults) {
411
- allIssues.push(...issues);
412
- }
413
-
414
- // ========== CREATE PAGE AUDIT ==========
415
- pages.push({
416
- url,
417
- statusCode: 200,
418
- title: onPageResult.data.title,
419
- description: onPageResult.data.description,
420
- canonical: onPageResult.data.canonical,
421
- h1: onPageResult.data.h1s,
422
- wordCount: onPageResult.data.wordCount,
423
- loadTime: perfData.loadTime,
424
- issues: allIssues.map(i => i.code),
425
- });
426
-
427
- console.error('\n✅ Audit complete!\n');
428
-
429
- return createReport(url, domain, allIssues, pages);
430
- }
431
-
432
- function createReport(url: string, domain: string, issues: AuditIssue[], pages: PageAudit[]): AuditReport {
433
- // Calculate health score
434
- const healthScore = calculateHealthScore(issues);
435
-
436
- // Count issues by severity
437
- const errors = issues.filter(i => i.severity === 'error').length;
438
- const warnings = issues.filter(i => i.severity === 'warning').length;
439
- const notices = issues.filter(i => i.severity === 'notice').length;
440
-
441
- // Calculate passed checks (rough estimate based on possible checks)
442
- const totalPossibleChecks = 310; // Approximate number of checks (expanded with Rank Math parity + URL safety checks)
443
- const passed = Math.max(0, totalPossibleChecks - errors - warnings);
444
-
445
- return {
446
- url,
447
- domain,
448
- timestamp: new Date().toISOString(),
449
- crawlStats: {
450
- totalUrls: 1,
451
- crawledUrls: 1,
452
- errorUrls: 0,
453
- redirectUrls: 0,
454
- blockedUrls: 0,
455
- },
456
- healthScore,
457
- issues,
458
- pages,
459
- summary: {
460
- errors,
461
- warnings,
462
- notices,
463
- passed,
464
- },
465
- };
466
- }
467
-
468
- function calculateHealthScore(issues: AuditIssue[]): HealthScore {
469
- // Weight issues by severity
470
- const weights: Record<string, number> = { critical: 15, error: 10, warning: 3, info: 1, notice: 1 };
471
-
472
- // Calculate deductions per category
473
- const categoryDeductions: Record<string, number> = {
474
- crawlability: 0,
475
- indexability: 0,
476
- 'on-page': 0,
477
- content: 0,
478
- links: 0,
479
- images: 0,
480
- 'structured-data': 0,
481
- performance: 0,
482
- security: 0,
483
- mobile: 0,
484
- international: 0,
485
- 'ai-readiness': 0,
486
- 'social': 0,
487
- 'local-seo': 0,
488
- 'accessibility': 0,
489
- };
490
-
491
- for (const issue of issues) {
492
- const deduction = weights[issue.severity];
493
- categoryDeductions[issue.category] = (categoryDeductions[issue.category] || 0) + deduction;
494
- }
495
-
496
- // Calculate scores (100 - deductions, min 0)
497
- const crawlability = Math.max(0, 100 - categoryDeductions.crawlability * 5);
498
- const indexability = Math.max(0, 100 - categoryDeductions.indexability * 5);
499
- const onPage = Math.max(0, 100 - categoryDeductions['on-page'] * 3);
500
- const content = Math.max(0, 100 - categoryDeductions.content * 3);
501
- const links = Math.max(0, 100 - categoryDeductions.links * 3);
502
- const performance = Math.max(0, 100 - categoryDeductions.performance * 4);
503
- const security = Math.max(0, 100 - categoryDeductions.security * 5);
504
- const aiReadiness = Math.max(0, 100 - categoryDeductions['ai-readiness'] * 3);
505
- const social = Math.max(0, 100 - categoryDeductions.social * 2);
506
- const localSeo = Math.max(0, 100 - categoryDeductions['local-seo'] * 2);
507
- const accessibility = Math.max(0, 100 - categoryDeductions.accessibility * 4);
508
-
509
- // Overall score is weighted average
510
- const overall = Math.round(
511
- (crawlability * 0.11 +
512
- indexability * 0.11 +
513
- onPage * 0.16 +
514
- content * 0.07 +
515
- links * 0.07 +
516
- performance * 0.11 +
517
- security * 0.11 +
518
- aiReadiness * 0.06 +
519
- social * 0.05 +
520
- localSeo * 0.05 +
521
- accessibility * 0.10)
522
- );
523
-
524
- return {
525
- overall,
526
- crawlability,
527
- indexability,
528
- onPage,
529
- content,
530
- links,
531
- performance,
532
- security,
533
- aiReadiness,
534
- social,
535
- localSeo,
536
- accessibility,
537
- };
538
- }
539
-
540
- // Format the report for console output
541
- export function formatReport(report: AuditReport): string {
542
- const lines: string[] = [];
543
-
544
- lines.push('');
545
- lines.push('═'.repeat(70));
546
- lines.push(' SEO AUDIT REPORT');
547
- lines.push('═'.repeat(70));
548
- lines.push(` URL: ${report.url}`);
549
- lines.push(` Domain: ${report.domain}`);
550
- lines.push(` Time: ${report.timestamp}`);
551
- lines.push('═'.repeat(70));
552
- lines.push('');
553
-
554
- // Health Score
555
- lines.push('📊 HEALTH SCORE');
556
- lines.push('─'.repeat(70));
557
- lines.push(` Overall: ${report.healthScore.overall}/100 ${getScoreEmoji(report.healthScore.overall)}`);
558
- lines.push('');
559
- lines.push(' Category Scores:');
560
- lines.push(` Crawlability: ${report.healthScore.crawlability}/100`);
561
- lines.push(` Indexability: ${report.healthScore.indexability}/100`);
562
- lines.push(` On-Page SEO: ${report.healthScore.onPage}/100`);
563
- lines.push(` Content: ${report.healthScore.content}/100`);
564
- lines.push(` Links: ${report.healthScore.links}/100`);
565
- lines.push(` Performance: ${report.healthScore.performance}/100`);
566
- lines.push(` Security: ${report.healthScore.security}/100`);
567
- lines.push(` AI Readiness: ${report.healthScore.aiReadiness}/100`);
568
- lines.push(` Social: ${report.healthScore.social}/100`);
569
- lines.push(` Local SEO: ${report.healthScore.localSeo}/100`);
570
- lines.push(` Accessibility: ${report.healthScore.accessibility}/100`);
571
- lines.push('');
572
-
573
- // Summary
574
- lines.push('📋 SUMMARY');
575
- lines.push('─'.repeat(70));
576
- lines.push(` ❌ Errors: ${report.summary.errors}`);
577
- lines.push(` ⚠️ Warnings: ${report.summary.warnings}`);
578
- lines.push(` ℹ️ Notices: ${report.summary.notices}`);
579
- lines.push(` ✅ Passed: ${report.summary.passed}`);
580
- lines.push('');
581
-
582
- // Issues by category
583
- const issuesByCategory = groupIssuesByCategory(report.issues);
584
-
585
- for (const [category, issues] of Object.entries(issuesByCategory)) {
586
- if (issues.length === 0) continue;
587
-
588
- lines.push(`📁 ${category.toUpperCase()}`);
589
- lines.push('─'.repeat(70));
590
-
591
- for (const issue of issues) {
592
- const icon = issue.severity === 'error' ? '❌' : issue.severity === 'warning' ? '⚠️' : 'ℹ️';
593
- lines.push(` ${icon} ${issue.title}`);
594
- lines.push(` ${issue.description}`);
595
- if (issue.howToFix) {
596
- lines.push(` → ${issue.howToFix}`);
597
- }
598
- lines.push('');
599
- }
600
- }
601
-
602
- lines.push('═'.repeat(70));
603
-
604
- return lines.join('\n');
605
- }
606
-
607
- function getScoreEmoji(score: number): string {
608
- if (score >= 90) return '🟢 Excellent';
609
- if (score >= 70) return '🟡 Good';
610
- if (score >= 50) return '🟠 Needs Work';
611
- return '🔴 Poor';
612
- }
613
-
614
- function groupIssuesByCategory(issues: AuditIssue[]): Record<string, AuditIssue[]> {
615
- const grouped: Record<string, AuditIssue[]> = {};
616
-
617
- for (const issue of issues) {
618
- if (!grouped[issue.category]) {
619
- grouped[issue.category] = [];
620
- }
621
- grouped[issue.category].push(issue);
622
- }
623
-
624
- // Sort by severity within each category
625
- for (const category of Object.keys(grouped)) {
626
- grouped[category].sort((a, b) => {
627
- const order: Record<string, number> = { critical: 0, error: 1, warning: 2, info: 3, notice: 4 };
628
- return (order[a.severity] ?? 5) - (order[b.severity] ?? 5);
629
- });
630
- }
631
-
632
- return grouped;
633
- }