@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,302 @@
1
+ /**
2
+ * Isomorphic HTTP utilities using native fetch
3
+ * Works in both Node.js (18+) and Deno
4
+ */
5
+
6
+ export interface HttpRequestConfig {
7
+ headers?: Record<string, string>;
8
+ timeout?: number;
9
+ validateStatus?: (status: number) => boolean;
10
+ maxRedirects?: number;
11
+ auth?: { username: string; password: string };
12
+ params?: Record<string, string | number | boolean>;
13
+ }
14
+
15
+ export interface HttpResponse<T = unknown> {
16
+ data: T;
17
+ status: number;
18
+ statusText: string;
19
+ headers: Record<string, string>;
20
+ }
21
+
22
+ const DEFAULT_TIMEOUT = 30000;
23
+ const DEFAULT_USER_AGENT = 'RankCLI/1.0 (+https://rankcli.dev)';
24
+
25
+ /**
26
+ * Create an AbortController with timeout
27
+ */
28
+ function createTimeoutController(timeout: number): { controller: AbortController; timeoutId: ReturnType<typeof setTimeout> } {
29
+ const controller = new AbortController();
30
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
31
+ return { controller, timeoutId };
32
+ }
33
+
34
+ /**
35
+ * Convert Headers to plain object
36
+ */
37
+ function headersToObject(headers: Headers): Record<string, string> {
38
+ const obj: Record<string, string> = {};
39
+ headers.forEach((value, key) => {
40
+ obj[key.toLowerCase()] = value;
41
+ });
42
+ return obj;
43
+ }
44
+
45
+ /**
46
+ * HTTP GET request
47
+ */
48
+ export async function httpGet<T = string>(
49
+ url: string,
50
+ config: HttpRequestConfig = {}
51
+ ): Promise<HttpResponse<T>> {
52
+ const {
53
+ headers = {},
54
+ timeout = DEFAULT_TIMEOUT,
55
+ validateStatus = (status) => status >= 200 && status < 300,
56
+ maxRedirects = 5,
57
+ params,
58
+ } = config;
59
+
60
+ // Build URL with query params
61
+ let finalUrl = url;
62
+ if (params) {
63
+ const searchParams = new URLSearchParams();
64
+ for (const [key, value] of Object.entries(params)) {
65
+ searchParams.append(key, String(value));
66
+ }
67
+ const separator = url.includes('?') ? '&' : '?';
68
+ finalUrl = `${url}${separator}${searchParams.toString()}`;
69
+ }
70
+
71
+ const { controller, timeoutId } = createTimeoutController(timeout);
72
+
73
+ try {
74
+ const response = await fetch(finalUrl, {
75
+ method: 'GET',
76
+ headers: {
77
+ 'User-Agent': DEFAULT_USER_AGENT,
78
+ ...headers,
79
+ },
80
+ signal: controller.signal,
81
+ redirect: maxRedirects > 0 ? 'follow' : 'manual',
82
+ });
83
+
84
+ clearTimeout(timeoutId);
85
+
86
+ const contentType = response.headers.get('content-type') || '';
87
+ let data: T;
88
+
89
+ if (contentType.includes('application/json')) {
90
+ data = await response.json() as T;
91
+ } else {
92
+ data = await response.text() as unknown as T;
93
+ }
94
+
95
+ if (!validateStatus(response.status)) {
96
+ const error = new Error(`Request failed with status ${response.status}`) as Error & { response: HttpResponse<T> };
97
+ error.response = {
98
+ data,
99
+ status: response.status,
100
+ statusText: response.statusText,
101
+ headers: headersToObject(response.headers),
102
+ };
103
+ throw error;
104
+ }
105
+
106
+ return {
107
+ data,
108
+ status: response.status,
109
+ statusText: response.statusText,
110
+ headers: headersToObject(response.headers),
111
+ };
112
+ } catch (error) {
113
+ clearTimeout(timeoutId);
114
+ if (error instanceof Error && error.name === 'AbortError') {
115
+ throw new Error(`Request timeout after ${timeout}ms`);
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * HTTP HEAD request
123
+ */
124
+ export async function httpHead(
125
+ url: string,
126
+ config: HttpRequestConfig = {}
127
+ ): Promise<HttpResponse<null>> {
128
+ const {
129
+ headers = {},
130
+ timeout = DEFAULT_TIMEOUT,
131
+ validateStatus = (status) => status >= 200 && status < 300,
132
+ maxRedirects = 5,
133
+ } = config;
134
+
135
+ const { controller, timeoutId } = createTimeoutController(timeout);
136
+
137
+ try {
138
+ const response = await fetch(url, {
139
+ method: 'HEAD',
140
+ headers: {
141
+ 'User-Agent': DEFAULT_USER_AGENT,
142
+ ...headers,
143
+ },
144
+ signal: controller.signal,
145
+ redirect: maxRedirects > 0 ? 'follow' : 'manual',
146
+ });
147
+
148
+ clearTimeout(timeoutId);
149
+
150
+ if (!validateStatus(response.status)) {
151
+ const error = new Error(`Request failed with status ${response.status}`) as Error & { response: HttpResponse<null> };
152
+ error.response = {
153
+ data: null,
154
+ status: response.status,
155
+ statusText: response.statusText,
156
+ headers: headersToObject(response.headers),
157
+ };
158
+ throw error;
159
+ }
160
+
161
+ return {
162
+ data: null,
163
+ status: response.status,
164
+ statusText: response.statusText,
165
+ headers: headersToObject(response.headers),
166
+ };
167
+ } catch (error) {
168
+ clearTimeout(timeoutId);
169
+ if (error instanceof Error && error.name === 'AbortError') {
170
+ throw new Error(`Request timeout after ${timeout}ms`);
171
+ }
172
+ throw error;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * HTTP POST request
178
+ */
179
+ export async function httpPost<T = unknown>(
180
+ url: string,
181
+ data?: unknown,
182
+ config: HttpRequestConfig = {}
183
+ ): Promise<HttpResponse<T>> {
184
+ const {
185
+ headers = {},
186
+ timeout = DEFAULT_TIMEOUT,
187
+ validateStatus = (status) => status >= 200 && status < 300,
188
+ auth,
189
+ } = config;
190
+
191
+ const { controller, timeoutId } = createTimeoutController(timeout);
192
+
193
+ const requestHeaders: Record<string, string> = {
194
+ 'User-Agent': DEFAULT_USER_AGENT,
195
+ 'Content-Type': 'application/json',
196
+ ...headers,
197
+ };
198
+
199
+ // Add basic auth if provided
200
+ if (auth) {
201
+ const credentials = btoa(`${auth.username}:${auth.password}`);
202
+ requestHeaders['Authorization'] = `Basic ${credentials}`;
203
+ }
204
+
205
+ try {
206
+ const response = await fetch(url, {
207
+ method: 'POST',
208
+ headers: requestHeaders,
209
+ body: data ? JSON.stringify(data) : undefined,
210
+ signal: controller.signal,
211
+ });
212
+
213
+ clearTimeout(timeoutId);
214
+
215
+ const contentType = response.headers.get('content-type') || '';
216
+ let responseData: T;
217
+
218
+ if (contentType.includes('application/json')) {
219
+ responseData = await response.json() as T;
220
+ } else {
221
+ responseData = await response.text() as unknown as T;
222
+ }
223
+
224
+ if (!validateStatus(response.status)) {
225
+ const error = new Error(`Request failed with status ${response.status}`) as Error & { response: HttpResponse<T> };
226
+ error.response = {
227
+ data: responseData,
228
+ status: response.status,
229
+ statusText: response.statusText,
230
+ headers: headersToObject(response.headers),
231
+ };
232
+ throw error;
233
+ }
234
+
235
+ return {
236
+ data: responseData,
237
+ status: response.status,
238
+ statusText: response.statusText,
239
+ headers: headersToObject(response.headers),
240
+ };
241
+ } catch (error) {
242
+ clearTimeout(timeoutId);
243
+ if (error instanceof Error && error.name === 'AbortError') {
244
+ throw new Error(`Request timeout after ${timeout}ms`);
245
+ }
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Follow redirects manually and return the chain
252
+ */
253
+ export async function followRedirects(
254
+ url: string,
255
+ config: HttpRequestConfig = {}
256
+ ): Promise<{ chain: string[]; finalUrl: string; status: number }> {
257
+ const {
258
+ headers = {},
259
+ timeout = DEFAULT_TIMEOUT,
260
+ maxRedirects = 10,
261
+ } = config;
262
+
263
+ const chain: string[] = [url];
264
+ let currentUrl = url;
265
+ let redirectCount = 0;
266
+
267
+ while (redirectCount < maxRedirects) {
268
+ const { controller, timeoutId } = createTimeoutController(timeout);
269
+
270
+ try {
271
+ const response = await fetch(currentUrl, {
272
+ method: 'HEAD',
273
+ headers: {
274
+ 'User-Agent': DEFAULT_USER_AGENT,
275
+ ...headers,
276
+ },
277
+ signal: controller.signal,
278
+ redirect: 'manual',
279
+ });
280
+
281
+ clearTimeout(timeoutId);
282
+
283
+ if (response.status >= 300 && response.status < 400) {
284
+ const location = response.headers.get('location');
285
+ if (location) {
286
+ // Resolve relative URLs
287
+ currentUrl = new URL(location, currentUrl).toString();
288
+ chain.push(currentUrl);
289
+ redirectCount++;
290
+ continue;
291
+ }
292
+ }
293
+
294
+ return { chain, finalUrl: currentUrl, status: response.status };
295
+ } catch (error) {
296
+ clearTimeout(timeoutId);
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ return { chain, finalUrl: currentUrl, status: 0 };
302
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * WASM Adapter
3
+ *
4
+ * This file creates the global RankCLI object that the WASM wrapper expects.
5
+ * It adapts the async runFullAudit to a sync-compatible interface for VM execution.
6
+ *
7
+ * When this code is embedded in WASM and executed in a Node.js VM:
8
+ * - The VM has limited async capabilities
9
+ * - We provide a sync interface that queues work for the host
10
+ */
11
+
12
+ import * as cheerio from 'cheerio';
13
+ import { analyzeOnPage } from './audit/checks/on-page.js';
14
+ import { analyzeStructuredData } from './audit/checks/structured-data.js';
15
+ import { analyzeMobile } from './audit/checks/mobile.js';
16
+ import { analyzeSocialMeta } from './audit/checks/social-meta.js';
17
+ import { analyzeSERPPreview } from './audit/checks/serp-preview.js';
18
+ import { analyzeDOMStructure } from './audit/checks/dom-analysis.js';
19
+ import { detectTechnologies } from './audit/checks/tech-detection.js';
20
+ import { analyzeKeywords } from './audit/checks/keyword-analysis.js';
21
+ import { analyzeDomSize } from './audit/checks/dom-size.js';
22
+ import { analyzeImageDimensions } from './audit/checks/image-dimensions.js';
23
+ import { analyzeUrlSafety } from './audit/checks/url-safety.js';
24
+ import { detectSoft404 } from './audit/checks/soft-404.js';
25
+ import { analyzePagination } from './audit/checks/pagination.js';
26
+ // Extended checks
27
+ import { analyzeAnchorText } from './audit/checks/anchor-text.js';
28
+ import { analyzeLocalSEO } from './audit/checks/local-seo.js';
29
+ import { analyzeModernImages } from './audit/checks/modern-images.js';
30
+ import { analyzeResourceHints } from './audit/checks/resource-hints.js';
31
+ import { analyzeTrackerBloat } from './audit/checks/tracker-bloat.js';
32
+ import { analyzeClientRendering } from './audit/checks/client-rendering.js';
33
+ import { analyzeResponsiveImages } from './audit/checks/responsive-images.js';
34
+ import { analyzeColorContrast } from './audit/checks/color-contrast.js';
35
+ import { analyzePageResources } from './audit/checks/page-resources.js';
36
+ // Premium checks
37
+ import { analyzeFeaturedSnippet } from './audit/checks/featured-snippet.js';
38
+ import { analyzeInternalLinkGraph } from './audit/checks/internal-link-graph.js';
39
+ import { analyzeEEATSignals } from './audit/checks/eeat-signals.js';
40
+ import { analyzeContentScience } from './audit/checks/content-science.js';
41
+ import { analyzeConversionElements } from './audit/checks/conversion-elements.js';
42
+ import { analyzeKeywordPlacement } from './audit/checks/keyword-placement.js';
43
+ import { analyzeNavBoostSignals } from './audit/checks/navboost-signals.js';
44
+ import { analyzeEntitySEO } from './audit/checks/entity-seo.js';
45
+ import { analyzeFreshnessSignals } from './audit/checks/freshness-signals.js';
46
+ import { analyzeAIContentStructure } from './audit/checks/ai-content-structure.js';
47
+ import { analyzeCitationQuality } from './audit/checks/citation-quality.js';
48
+ import { analyzeAnswerConciseness } from './audit/checks/answer-conciseness.js';
49
+ import { analyzeBrandMentionOptimization } from './audit/checks/brand-mention-optimization.js';
50
+ import { analyzeAICitationWorthiness } from './audit/checks/ai-citation-worthiness.js';
51
+ import { analyzeReviewEcosystem } from './audit/checks/review-ecosystem.js';
52
+ import type { AuditIssue } from './audit/types.js';
53
+
54
+ interface SyncAuditResult {
55
+ success: boolean;
56
+ score: number;
57
+ issues: AuditIssue[];
58
+ checksRun: number;
59
+ tier: string;
60
+ }
61
+
62
+ /**
63
+ * Synchronous audit function for VM execution
64
+ * Only runs checks that don't require network requests
65
+ */
66
+ function runSyncAudit(url: string, html: string, checksLimit: number): SyncAuditResult {
67
+ const allIssues: AuditIssue[] = [];
68
+ const headers: Record<string, string> = {};
69
+
70
+ // Determine tier
71
+ const tier = checksLimit > 100 ? 'paid' : checksLimit > 50 ? 'registered' : 'free';
72
+ const runExtended = checksLimit > 50;
73
+ const runPremium = checksLimit > 100;
74
+
75
+ // ===== CORE CHECKS (all tiers) =====
76
+ try {
77
+ const onPage = analyzeOnPage(html, url);
78
+ allIssues.push(...onPage.issues);
79
+ } catch (e) { /* ignore */ }
80
+
81
+ try {
82
+ const structured = analyzeStructuredData(html, url);
83
+ allIssues.push(...structured.issues);
84
+ } catch (e) { /* ignore */ }
85
+
86
+ try {
87
+ const mobile = analyzeMobile(html, url);
88
+ allIssues.push(...mobile.issues);
89
+ } catch (e) { /* ignore */ }
90
+
91
+ try {
92
+ const social = analyzeSocialMeta(html, url);
93
+ allIssues.push(...social.issues);
94
+ } catch (e) { /* ignore */ }
95
+
96
+ try {
97
+ const serp = analyzeSERPPreview(html, url);
98
+ allIssues.push(...serp.issues);
99
+ } catch (e) { /* ignore */ }
100
+
101
+ try {
102
+ const dom = analyzeDOMStructure(html, url);
103
+ allIssues.push(...dom.issues);
104
+ } catch (e) { /* ignore */ }
105
+
106
+ try {
107
+ const tech = detectTechnologies(html, url, headers);
108
+ allIssues.push(...tech.issues);
109
+ } catch (e) { /* ignore */ }
110
+
111
+ try {
112
+ const keywords = analyzeKeywords(html, url);
113
+ allIssues.push(...keywords.issues);
114
+ } catch (e) { /* ignore */ }
115
+
116
+ try {
117
+ const domSize = analyzeDomSize(html, url);
118
+ allIssues.push(...domSize.issues);
119
+ } catch (e) { /* ignore */ }
120
+
121
+ try {
122
+ const imageDims = analyzeImageDimensions(html, url);
123
+ allIssues.push(...imageDims.issues);
124
+ } catch (e) { /* ignore */ }
125
+
126
+ try {
127
+ const urlSafety = analyzeUrlSafety(url);
128
+ allIssues.push(...urlSafety.issues);
129
+ } catch (e) { /* ignore */ }
130
+
131
+ try {
132
+ const soft404 = detectSoft404(html, url, 200);
133
+ allIssues.push(...soft404.issues);
134
+ } catch (e) { /* ignore */ }
135
+
136
+ let coreChecks = 12;
137
+
138
+ // ===== EXTENDED CHECKS (registered + paid) =====
139
+ if (runExtended) {
140
+ try {
141
+ const anchor = analyzeAnchorText(html, url);
142
+ allIssues.push(...anchor.issues);
143
+ } catch (e) { /* ignore */ }
144
+
145
+ try {
146
+ const local = analyzeLocalSEO(html, url);
147
+ allIssues.push(...local.issues);
148
+ } catch (e) { /* ignore */ }
149
+
150
+ try {
151
+ const modern = analyzeModernImages(html, url);
152
+ allIssues.push(...modern.issues);
153
+ } catch (e) { /* ignore */ }
154
+
155
+ try {
156
+ const hints = analyzeResourceHints(html, url);
157
+ allIssues.push(...hints.issues);
158
+ } catch (e) { /* ignore */ }
159
+
160
+ try {
161
+ const tracker = analyzeTrackerBloat(html, url);
162
+ allIssues.push(...tracker.issues);
163
+ } catch (e) { /* ignore */ }
164
+
165
+ try {
166
+ const csr = analyzeClientRendering(html, url);
167
+ allIssues.push(...csr.issues);
168
+ } catch (e) { /* ignore */ }
169
+
170
+ try {
171
+ const responsive = analyzeResponsiveImages(html, url);
172
+ allIssues.push(...responsive.issues);
173
+ } catch (e) { /* ignore */ }
174
+
175
+ try {
176
+ const contrast = analyzeColorContrast(html, url);
177
+ allIssues.push(...contrast.issues);
178
+ } catch (e) { /* ignore */ }
179
+
180
+ try {
181
+ const resources = analyzePageResources(html, url);
182
+ allIssues.push(...resources.issues);
183
+ } catch (e) { /* ignore */ }
184
+
185
+ coreChecks += 9;
186
+ }
187
+
188
+ // ===== PREMIUM CHECKS (paid only) =====
189
+ if (runPremium) {
190
+ try {
191
+ const snippet = analyzeFeaturedSnippet(html, url);
192
+ allIssues.push(...snippet.issues);
193
+ } catch (e) { /* ignore */ }
194
+
195
+ try {
196
+ const linkGraph = analyzeInternalLinkGraph(html, url);
197
+ allIssues.push(...linkGraph.issues);
198
+ } catch (e) { /* ignore */ }
199
+
200
+ try {
201
+ const eeat = analyzeEEATSignals(html, url);
202
+ allIssues.push(...eeat.issues);
203
+ } catch (e) { /* ignore */ }
204
+
205
+ try {
206
+ const content = analyzeContentScience(html, url);
207
+ allIssues.push(...content.issues);
208
+ } catch (e) { /* ignore */ }
209
+
210
+ try {
211
+ const conversion = analyzeConversionElements(html, url);
212
+ allIssues.push(...conversion.issues);
213
+ } catch (e) { /* ignore */ }
214
+
215
+ try {
216
+ const placement = analyzeKeywordPlacement(html, url);
217
+ allIssues.push(...placement.issues);
218
+ } catch (e) { /* ignore */ }
219
+
220
+ try {
221
+ const navboost = analyzeNavBoostSignals(html, url);
222
+ allIssues.push(...navboost.issues);
223
+ } catch (e) { /* ignore */ }
224
+
225
+ try {
226
+ const entity = analyzeEntitySEO(html, url);
227
+ allIssues.push(...entity.issues);
228
+ } catch (e) { /* ignore */ }
229
+
230
+ try {
231
+ const freshness = analyzeFreshnessSignals(html, url);
232
+ allIssues.push(...freshness.issues);
233
+ } catch (e) { /* ignore */ }
234
+
235
+ try {
236
+ const aiContent = analyzeAIContentStructure(html, url);
237
+ allIssues.push(...aiContent.issues);
238
+ } catch (e) { /* ignore */ }
239
+
240
+ try {
241
+ const citation = analyzeCitationQuality(html, url);
242
+ allIssues.push(...citation.issues);
243
+ } catch (e) { /* ignore */ }
244
+
245
+ try {
246
+ const answer = analyzeAnswerConciseness(html, url);
247
+ allIssues.push(...answer.issues);
248
+ } catch (e) { /* ignore */ }
249
+
250
+ try {
251
+ const brand = analyzeBrandMentionOptimization(html, url);
252
+ allIssues.push(...brand.issues);
253
+ } catch (e) { /* ignore */ }
254
+
255
+ try {
256
+ const worthiness = analyzeAICitationWorthiness(html, url);
257
+ allIssues.push(...worthiness.issues);
258
+ } catch (e) { /* ignore */ }
259
+
260
+ try {
261
+ const review = analyzeReviewEcosystem(html, url);
262
+ allIssues.push(...review.issues);
263
+ } catch (e) { /* ignore */ }
264
+
265
+ coreChecks += 15;
266
+ }
267
+
268
+ // Calculate score
269
+ const errors = allIssues.filter(i => i.severity === 'error').length;
270
+ const warnings = allIssues.filter(i => i.severity === 'warning').length;
271
+ const score = Math.max(0, Math.min(100, 100 - (errors * 10) - (warnings * 3)));
272
+
273
+ return {
274
+ success: true,
275
+ score,
276
+ issues: allIssues,
277
+ checksRun: coreChecks,
278
+ tier,
279
+ };
280
+ }
281
+
282
+ // Create global RankCLI object for VM execution
283
+ declare const globalThis: {
284
+ RankCLI?: {
285
+ runAudit: typeof runSyncAudit;
286
+ version: string;
287
+ };
288
+ };
289
+
290
+ if (typeof globalThis !== 'undefined') {
291
+ globalThis.RankCLI = {
292
+ runAudit: runSyncAudit,
293
+ version: '0.1.0',
294
+ };
295
+ }
296
+
297
+ export { runSyncAudit };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * WASM Entry Point
3
+ *
4
+ * This is the entry point for the code that gets embedded in WASM.
5
+ * It's a minimal build that only includes sync checks (no network requests).
6
+ *
7
+ * Build with: tsup src/wasm-entry.ts --format esm --minify
8
+ */
9
+
10
+ // Import and re-export the WASM adapter
11
+ import './wasm-adapter.js';
12
+
13
+ // Export types that might be needed
14
+ export type { AuditIssue, AuditReport } from './audit/types.js';
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/wasm-adapter.ts'],
5
+ outDir: 'dist/wasm',
6
+ format: ['iife'],
7
+ minify: true,
8
+ bundle: true,
9
+ // CRITICAL: Don't externalize anything - bundle everything inline
10
+ noExternal: [/.*/],
11
+ platform: 'browser',
12
+ target: 'es2020',
13
+ globalName: '__rankcli__',
14
+ esbuildOptions(options) {
15
+ // Set global RankCLI at the end
16
+ options.footer = {
17
+ js: `
18
+ if (typeof globalThis !== 'undefined') {
19
+ globalThis.RankCLI = __rankcli__.runSyncAudit ?
20
+ { runAudit: __rankcli__.runSyncAudit, version: '0.1.0' } :
21
+ __rankcli__;
22
+ }
23
+ `,
24
+ };
25
+ },
26
+ });
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['src/**/*.test.ts'],
8
+ coverage: {
9
+ provider: 'v8',
10
+ reporter: ['text', 'json', 'html'],
11
+ include: ['src/**/*.ts'],
12
+ exclude: ['src/**/*.test.ts', 'src/test/**'],
13
+ },
14
+ },
15
+ });