@rankcli/agent-runtime 0.0.1

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