@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,439 @@
1
+ // Additional SEO Checks
2
+ // AMP detection, ads.txt, DMARC, email privacy, and more
3
+
4
+ import { httpGet } from '../../utils/http.js';
5
+ import * as cheerio from 'cheerio';
6
+ import type { AuditIssue } from '../types.js';
7
+ import { ISSUE_DEFINITIONS } from '../types.js';
8
+
9
+ /**
10
+ * Isomorphic DNS TXT record lookup using DNS-over-HTTPS (Cloudflare)
11
+ * Works in both Node.js and Deno/browser environments
12
+ */
13
+ async function resolveTxtDoH(domain: string): Promise<string[][]> {
14
+ const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=TXT`;
15
+
16
+ const response = await fetch(url, {
17
+ headers: {
18
+ 'Accept': 'application/dns-json'
19
+ }
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`DNS lookup failed: ${response.status}`);
24
+ }
25
+
26
+ const data = await response.json() as { Answer?: Array<{ data: string }> };
27
+
28
+ if (!data.Answer || data.Answer.length === 0) {
29
+ return [];
30
+ }
31
+
32
+ // Parse TXT records - they come with quotes that need to be removed
33
+ return data.Answer.map(record => {
34
+ const txt = record.data.replace(/^"|"$/g, '');
35
+ return [txt];
36
+ });
37
+ }
38
+
39
+ export interface AdsTxtData {
40
+ exists: boolean;
41
+ valid: boolean;
42
+ entries: number;
43
+ errors: string[];
44
+ }
45
+
46
+ export interface DMARCData {
47
+ exists: boolean;
48
+ policy: string | null;
49
+ record: string | null;
50
+ }
51
+
52
+ export interface SPFData {
53
+ exists: boolean;
54
+ record: string | null;
55
+ }
56
+
57
+ export interface AMPData {
58
+ hasAmpLink: boolean;
59
+ ampUrl: string | null;
60
+ ampValid: boolean | null;
61
+ }
62
+
63
+ export interface AdditionalChecksData {
64
+ adsTxt: AdsTxtData;
65
+ dmarc: DMARCData;
66
+ spf: SPFData;
67
+ amp: AMPData;
68
+ plaintextEmails: string[];
69
+ hasAppleTouchIcon: boolean;
70
+ faviconFormat: string | null;
71
+ }
72
+
73
+ /**
74
+ * Check for ads.txt file
75
+ */
76
+ export async function checkAdsTxt(baseUrl: string): Promise<{ issues: AuditIssue[]; data: AdsTxtData }> {
77
+ const issues: AuditIssue[] = [];
78
+ const url = new URL('/ads.txt', baseUrl).href;
79
+
80
+ try {
81
+ const response = await httpGet<string>(url, {
82
+
83
+ timeout: 10000,
84
+ validateStatus: () => true,
85
+ });
86
+
87
+ if (response.status === 404 || response.status >= 400) {
88
+ // Not an error for most sites - only relevant for publishers
89
+ return {
90
+ issues,
91
+ data: { exists: false, valid: false, entries: 0, errors: [] },
92
+ };
93
+ }
94
+
95
+ const content = response.data as string;
96
+ const lines = content.split('\n').filter((line) => line.trim() && !line.trim().startsWith('#'));
97
+
98
+ // Basic validation
99
+ const errors: string[] = [];
100
+ let validEntries = 0;
101
+
102
+ for (const line of lines) {
103
+ // ads.txt format: domain, publisher-account-id, account-type, certification-authority-id
104
+ const parts = line.split(',').map((p) => p.trim());
105
+ if (parts.length >= 3) {
106
+ validEntries++;
107
+ } else if (line.trim()) {
108
+ errors.push(`Invalid line format: ${line.substring(0, 50)}`);
109
+ }
110
+ }
111
+
112
+ const valid = errors.length === 0 && validEntries > 0;
113
+
114
+ if (!valid && validEntries > 0) {
115
+ issues.push({
116
+ ...ISSUE_DEFINITIONS.ADS_TXT_INVALID,
117
+ affectedUrls: [url],
118
+ details: { errors, validEntries },
119
+ });
120
+ }
121
+
122
+ return {
123
+ issues,
124
+ data: { exists: true, valid, entries: validEntries, errors },
125
+ };
126
+ } catch {
127
+ return {
128
+ issues,
129
+ data: { exists: false, valid: false, entries: 0, errors: ['Failed to fetch'] },
130
+ };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Check DMARC DNS record
136
+ */
137
+ export async function checkDMARC(domain: string): Promise<{ issues: AuditIssue[]; data: DMARCData }> {
138
+ const issues: AuditIssue[] = [];
139
+
140
+ try {
141
+ // Extract domain from URL if needed
142
+ let cleanDomain = domain;
143
+ try {
144
+ cleanDomain = new URL(domain).hostname;
145
+ } catch {
146
+ // Already a domain
147
+ }
148
+
149
+ // Remove www prefix
150
+ cleanDomain = cleanDomain.replace(/^www\./, '');
151
+
152
+ // Query DMARC record using DNS-over-HTTPS
153
+ const dmarcDomain = `_dmarc.${cleanDomain}`;
154
+ const records = await resolveTxtDoH(dmarcDomain);
155
+
156
+ // Find DMARC record
157
+ const dmarcRecord = records.flat().find((r) => r.startsWith('v=DMARC1'));
158
+
159
+ if (dmarcRecord) {
160
+ // Parse policy
161
+ const policyMatch = dmarcRecord.match(/p=(\w+)/);
162
+ const policy = policyMatch ? policyMatch[1] : null;
163
+
164
+ return {
165
+ issues,
166
+ data: { exists: true, policy, record: dmarcRecord },
167
+ };
168
+ } else {
169
+ issues.push({
170
+ ...ISSUE_DEFINITIONS.DMARC_MISSING,
171
+ affectedUrls: [domain],
172
+ });
173
+ return {
174
+ issues,
175
+ data: { exists: false, policy: null, record: null },
176
+ };
177
+ }
178
+ } catch {
179
+ issues.push({
180
+ ...ISSUE_DEFINITIONS.DMARC_MISSING,
181
+ affectedUrls: [domain],
182
+ });
183
+ return {
184
+ issues,
185
+ data: { exists: false, policy: null, record: null },
186
+ };
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Check SPF DNS record
192
+ */
193
+ export async function checkSPF(domain: string): Promise<{ issues: AuditIssue[]; data: SPFData }> {
194
+ const issues: AuditIssue[] = [];
195
+
196
+ try {
197
+ let cleanDomain = domain;
198
+ try {
199
+ cleanDomain = new URL(domain).hostname;
200
+ } catch {
201
+ // Already a domain
202
+ }
203
+ cleanDomain = cleanDomain.replace(/^www\./, '');
204
+
205
+ const records = await resolveTxtDoH(cleanDomain);
206
+ const spfRecord = records.flat().find((r) => r.startsWith('v=spf1'));
207
+
208
+ if (spfRecord) {
209
+ return {
210
+ issues,
211
+ data: { exists: true, record: spfRecord },
212
+ };
213
+ } else {
214
+ issues.push({
215
+ ...ISSUE_DEFINITIONS.SPF_MISSING,
216
+ affectedUrls: [domain],
217
+ });
218
+ return {
219
+ issues,
220
+ data: { exists: false, record: null },
221
+ };
222
+ }
223
+ } catch {
224
+ // DNS lookup failed - could be many reasons
225
+ return {
226
+ issues,
227
+ data: { exists: false, record: null },
228
+ };
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Check for AMP version
234
+ */
235
+ export function checkAMP(html: string, url: string): { issues: AuditIssue[]; data: AMPData } {
236
+ const issues: AuditIssue[] = [];
237
+ const $ = cheerio.load(html);
238
+
239
+ // Check for amphtml link
240
+ const ampLink = $('link[rel="amphtml"]').attr('href');
241
+
242
+ if (ampLink) {
243
+ return {
244
+ issues,
245
+ data: {
246
+ hasAmpLink: true,
247
+ ampUrl: ampLink,
248
+ ampValid: null, // Would need to validate the AMP page
249
+ },
250
+ };
251
+ }
252
+
253
+ // Check if current page is AMP
254
+ const isAmpPage = $('html[amp]').length > 0 || $('html[⚡]').length > 0;
255
+
256
+ if (isAmpPage) {
257
+ return {
258
+ issues,
259
+ data: {
260
+ hasAmpLink: false,
261
+ ampUrl: url,
262
+ ampValid: null,
263
+ },
264
+ };
265
+ }
266
+
267
+ // Only flag AMP missing for content-heavy pages (articles, news)
268
+ const isArticle =
269
+ $('article').length > 0 ||
270
+ $('meta[property="og:type"][content="article"]').length > 0 ||
271
+ html.toLowerCase().includes('blog') ||
272
+ html.toLowerCase().includes('news');
273
+
274
+ if (isArticle) {
275
+ issues.push({
276
+ ...ISSUE_DEFINITIONS.AMP_MISSING,
277
+ affectedUrls: [url],
278
+ });
279
+ }
280
+
281
+ return {
282
+ issues,
283
+ data: {
284
+ hasAmpLink: false,
285
+ ampUrl: null,
286
+ ampValid: null,
287
+ },
288
+ };
289
+ }
290
+
291
+ /**
292
+ * Check for plaintext email addresses
293
+ */
294
+ export function checkPlaintextEmails(html: string, url: string): { issues: AuditIssue[]; emails: string[] } {
295
+ const issues: AuditIssue[] = [];
296
+ const $ = cheerio.load(html);
297
+
298
+ // Remove script and style content
299
+ $('script, style').remove();
300
+ const text = $('body').text();
301
+
302
+ // Email regex
303
+ const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
304
+ const emails = text.match(emailRegex) || [];
305
+
306
+ // Filter out common false positives
307
+ const filteredEmails = emails.filter((email) => {
308
+ const lowerEmail = email.toLowerCase();
309
+ return (
310
+ !lowerEmail.includes('example.com') &&
311
+ !lowerEmail.includes('yoursite.com') &&
312
+ !lowerEmail.includes('yourdomain') &&
313
+ !lowerEmail.endsWith('.png') &&
314
+ !lowerEmail.endsWith('.jpg')
315
+ );
316
+ });
317
+
318
+ // Deduplicate
319
+ const uniqueEmails = [...new Set(filteredEmails)];
320
+
321
+ if (uniqueEmails.length > 0) {
322
+ issues.push({
323
+ ...ISSUE_DEFINITIONS.PLAINTEXT_EMAIL,
324
+ affectedUrls: [url],
325
+ details: {
326
+ emails: uniqueEmails,
327
+ count: uniqueEmails.length,
328
+ },
329
+ });
330
+ }
331
+
332
+ return { issues, emails: uniqueEmails };
333
+ }
334
+
335
+ /**
336
+ * Check for Apple Touch Icon
337
+ */
338
+ export function checkAppleTouchIcon(html: string, url: string): { issues: AuditIssue[]; exists: boolean } {
339
+ const issues: AuditIssue[] = [];
340
+ const $ = cheerio.load(html);
341
+
342
+ const hasAppleTouchIcon =
343
+ $('link[rel="apple-touch-icon"]').length > 0 || $('link[rel="apple-touch-icon-precomposed"]').length > 0;
344
+
345
+ if (!hasAppleTouchIcon) {
346
+ issues.push({
347
+ ...ISSUE_DEFINITIONS.APPLE_TOUCH_ICON_MISSING,
348
+ affectedUrls: [url],
349
+ });
350
+ }
351
+
352
+ return { issues, exists: hasAppleTouchIcon };
353
+ }
354
+
355
+ /**
356
+ * Check favicon format
357
+ */
358
+ export function checkFaviconFormat(html: string, url: string): { issues: AuditIssue[]; format: string | null } {
359
+ const issues: AuditIssue[] = [];
360
+ const $ = cheerio.load(html);
361
+
362
+ // Check for favicon links
363
+ const faviconLink =
364
+ $('link[rel="icon"]').attr('href') ||
365
+ $('link[rel="shortcut icon"]').attr('href') ||
366
+ null;
367
+
368
+ if (!faviconLink) {
369
+ return { issues, format: null };
370
+ }
371
+
372
+ // Determine format
373
+ let format: string | null = null;
374
+ const lowerHref = faviconLink.toLowerCase();
375
+
376
+ if (lowerHref.endsWith('.ico')) {
377
+ format = 'ico';
378
+ } else if (lowerHref.endsWith('.png')) {
379
+ format = 'png';
380
+ } else if (lowerHref.endsWith('.svg')) {
381
+ format = 'svg';
382
+ } else if (lowerHref.includes('data:image/')) {
383
+ format = 'inline';
384
+ } else {
385
+ format = 'unknown';
386
+ }
387
+
388
+ // Note: Not having .ico is just a notice, not a critical issue
389
+ // Modern browsers support PNG/SVG favicons well
390
+
391
+ return { issues, format };
392
+ }
393
+
394
+ /**
395
+ * Run all additional checks
396
+ */
397
+ export async function runAdditionalChecks(
398
+ url: string,
399
+ html: string
400
+ ): Promise<{ issues: AuditIssue[]; data: AdditionalChecksData }> {
401
+ const allIssues: AuditIssue[] = [];
402
+
403
+ // Run parallel checks
404
+ const [adsTxtResult, dmarcResult, spfResult] = await Promise.all([
405
+ checkAdsTxt(url),
406
+ checkDMARC(url),
407
+ checkSPF(url),
408
+ ]);
409
+
410
+ allIssues.push(...adsTxtResult.issues);
411
+ allIssues.push(...dmarcResult.issues);
412
+ allIssues.push(...spfResult.issues);
413
+
414
+ // Run HTML-based checks
415
+ const ampResult = checkAMP(html, url);
416
+ allIssues.push(...ampResult.issues);
417
+
418
+ const emailResult = checkPlaintextEmails(html, url);
419
+ allIssues.push(...emailResult.issues);
420
+
421
+ const touchIconResult = checkAppleTouchIcon(html, url);
422
+ allIssues.push(...touchIconResult.issues);
423
+
424
+ const faviconResult = checkFaviconFormat(html, url);
425
+ allIssues.push(...faviconResult.issues);
426
+
427
+ return {
428
+ issues: allIssues,
429
+ data: {
430
+ adsTxt: adsTxtResult.data,
431
+ dmarc: dmarcResult.data,
432
+ spf: spfResult.data,
433
+ amp: ampResult.data,
434
+ plaintextEmails: emailResult.emails,
435
+ hasAppleTouchIcon: touchIconResult.exists,
436
+ faviconFormat: faviconResult.format,
437
+ },
438
+ };
439
+ }