@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,220 +0,0 @@
1
- import { httpGet } from '../../utils/http.js';
2
- import type { AuditIssue, PageData } from '../types.js';
3
- import { ISSUE_DEFINITIONS } from '../types.js';
4
-
5
- export interface RobotsTxtResult {
6
- exists: boolean;
7
- content?: string;
8
- blocksAll: boolean;
9
- hasSitemap: boolean;
10
- sitemapUrls: string[];
11
- }
12
-
13
- export interface SitemapResult {
14
- exists: boolean;
15
- url?: string;
16
- urlCount: number;
17
- errors: string[];
18
- }
19
-
20
- export async function checkRobotsTxt(baseUrl: string): Promise<{ issues: AuditIssue[]; data: RobotsTxtResult }> {
21
- const issues: AuditIssue[] = [];
22
- const url = new URL('/robots.txt', baseUrl).href;
23
-
24
- try {
25
- const response = await httpGet<string>(url, {
26
- timeout: 10000,
27
- validateStatus: () => true,
28
- });
29
-
30
- if (response.status === 404) {
31
- issues.push({
32
- ...ISSUE_DEFINITIONS.ROBOTS_TXT_MISSING,
33
- affectedUrls: [url],
34
- });
35
- return {
36
- issues,
37
- data: { exists: false, blocksAll: false, hasSitemap: false, sitemapUrls: [] }
38
- };
39
- }
40
-
41
- const content = response.data as string;
42
- const lines = content.toLowerCase().split('\n');
43
-
44
- // Check if robots.txt blocks all crawlers
45
- let blocksAll = false;
46
- let currentUserAgent = '';
47
- for (const line of lines) {
48
- const trimmed = line.trim();
49
- if (trimmed.startsWith('user-agent:')) {
50
- currentUserAgent = trimmed.split(':')[1].trim();
51
- }
52
- if (trimmed === 'disallow: /' && currentUserAgent === '*') {
53
- blocksAll = true;
54
- }
55
- }
56
-
57
- if (blocksAll) {
58
- issues.push({
59
- ...ISSUE_DEFINITIONS.ROBOTS_TXT_BLOCKS_ALL,
60
- affectedUrls: [url],
61
- });
62
- }
63
-
64
- // Check for sitemap references
65
- const sitemapUrls: string[] = [];
66
- for (const line of content.split('\n')) {
67
- const trimmed = line.trim().toLowerCase();
68
- if (trimmed.startsWith('sitemap:')) {
69
- const sitemapUrl = line.trim().substring(8).trim();
70
- sitemapUrls.push(sitemapUrl);
71
- }
72
- }
73
-
74
- return {
75
- issues,
76
- data: {
77
- exists: true,
78
- content,
79
- blocksAll,
80
- hasSitemap: sitemapUrls.length > 0,
81
- sitemapUrls,
82
- }
83
- };
84
- } catch (error) {
85
- issues.push({
86
- ...ISSUE_DEFINITIONS.ROBOTS_TXT_MISSING,
87
- affectedUrls: [url],
88
- details: { error: error instanceof Error ? error.message : 'Unknown error' },
89
- });
90
- return {
91
- issues,
92
- data: { exists: false, blocksAll: false, hasSitemap: false, sitemapUrls: [] }
93
- };
94
- }
95
- }
96
-
97
- export async function checkSitemap(baseUrl: string, robotsData: RobotsTxtResult): Promise<{ issues: AuditIssue[]; data: SitemapResult }> {
98
- const issues: AuditIssue[] = [];
99
-
100
- // Try sitemap URLs from robots.txt first, then standard locations
101
- const sitemapUrls = robotsData.sitemapUrls.length > 0
102
- ? robotsData.sitemapUrls
103
- : [
104
- new URL('/sitemap.xml', baseUrl).href,
105
- new URL('/sitemap_index.xml', baseUrl).href,
106
- ];
107
-
108
- for (const sitemapUrl of sitemapUrls) {
109
- try {
110
- const response = await httpGet<string>(sitemapUrl, {
111
- timeout: 10000,
112
- validateStatus: () => true,
113
- });
114
-
115
- if (response.status === 200 && (response.data as string).includes('<urlset') || (response.data as string).includes('<sitemapindex')) {
116
- // Count URLs in sitemap
117
- const content = response.data as string;
118
- const urlMatches = content.match(/<loc>/g);
119
- const urlCount = urlMatches ? urlMatches.length : 0;
120
-
121
- // Check if sitemap is referenced in robots.txt
122
- if (!robotsData.hasSitemap) {
123
- issues.push({
124
- ...ISSUE_DEFINITIONS.SITEMAP_NOT_IN_ROBOTS,
125
- affectedUrls: [sitemapUrl],
126
- });
127
- }
128
-
129
- return {
130
- issues,
131
- data: { exists: true, url: sitemapUrl, urlCount, errors: [] }
132
- };
133
- }
134
- } catch {
135
- // Try next URL
136
- }
137
- }
138
-
139
- // No sitemap found
140
- issues.push({
141
- ...ISSUE_DEFINITIONS.SITEMAP_MISSING,
142
- affectedUrls: [new URL('/sitemap.xml', baseUrl).href],
143
- });
144
-
145
- return {
146
- issues,
147
- data: { exists: false, urlCount: 0, errors: ['No sitemap found'] }
148
- };
149
- }
150
-
151
- export async function checkRedirects(url: string): Promise<{ issues: AuditIssue[]; chain: string[]; finalUrl: string }> {
152
- const issues: AuditIssue[] = [];
153
- const chain: string[] = [url];
154
- let currentUrl = url;
155
- let maxRedirects = 10;
156
-
157
- while (maxRedirects > 0) {
158
- try {
159
- const response = await httpGet<string>(currentUrl, {
160
- timeout: 10000,
161
- maxRedirects: 0,
162
- validateStatus: () => true,
163
- });
164
-
165
- if (response.status >= 300 && response.status < 400) {
166
- const location = response.headers.location;
167
- if (!location) break;
168
-
169
- const nextUrl = new URL(location, currentUrl).href;
170
-
171
- // Check for redirect loop
172
- if (chain.includes(nextUrl)) {
173
- issues.push({
174
- ...ISSUE_DEFINITIONS.REDIRECT_LOOP,
175
- affectedUrls: chain,
176
- details: { chain, loopTo: nextUrl },
177
- });
178
- break;
179
- }
180
-
181
- chain.push(nextUrl);
182
- currentUrl = nextUrl;
183
- maxRedirects--;
184
- } else {
185
- break;
186
- }
187
- } catch {
188
- break;
189
- }
190
- }
191
-
192
- // Check for redirect chain (more than 1 redirect)
193
- if (chain.length > 2) {
194
- issues.push({
195
- ...ISSUE_DEFINITIONS.REDIRECT_CHAIN,
196
- affectedUrls: chain,
197
- details: { chain, hops: chain.length - 1 },
198
- });
199
- }
200
-
201
- return { issues, chain, finalUrl: currentUrl };
202
- }
203
-
204
- export async function runCrawlabilityChecks(baseUrl: string): Promise<AuditIssue[]> {
205
- const allIssues: AuditIssue[] = [];
206
-
207
- // Check robots.txt
208
- const robotsResult = await checkRobotsTxt(baseUrl);
209
- allIssues.push(...robotsResult.issues);
210
-
211
- // Check sitemap
212
- const sitemapResult = await checkSitemap(baseUrl, robotsResult.data);
213
- allIssues.push(...sitemapResult.issues);
214
-
215
- // Check for redirects on the main URL
216
- const redirectResult = await checkRedirects(baseUrl);
217
- allIssues.push(...redirectResult.issues);
218
-
219
- return allIssues;
220
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * Directory Listing Security Check
3
- *
4
- * Checks if the web server is configured to allow directory listing.
5
- * When enabled, accessing a directory without an index file shows
6
- * all files in that directory - a security risk.
7
- *
8
- * Detection methods:
9
- * - Check common directories (/images/, /assets/, /uploads/, etc.)
10
- * - Look for Apache/Nginx directory listing HTML patterns
11
- * - Check response headers
12
- */
13
-
14
- import { httpGet } from '../../utils/http.js';
15
- import type { AuditIssue } from '../types.js';
16
-
17
- export interface DirectoryListingData {
18
- directoryListingEnabled: boolean;
19
- vulnerableDirectories: string[];
20
- checkedDirectories: string[];
21
- serverType?: string;
22
- }
23
-
24
- // Common directories to check for directory listing
25
- const COMMON_DIRECTORIES = [
26
- '/images/',
27
- '/img/',
28
- '/assets/',
29
- '/static/',
30
- '/uploads/',
31
- '/files/',
32
- '/media/',
33
- '/css/',
34
- '/js/',
35
- '/scripts/',
36
- '/fonts/',
37
- '/backup/',
38
- '/temp/',
39
- '/tmp/',
40
- ];
41
-
42
- // Patterns that indicate directory listing is enabled
43
- const DIRECTORY_LISTING_PATTERNS = [
44
- // Apache
45
- /<title>Index of/i,
46
- /Parent Directory/i,
47
- /<h1>Index of/i,
48
- /\[DIR\]/i,
49
- /\[PARENTDIR\]/i,
50
- // Nginx
51
- /<title>Index of.*<\/title>/i,
52
- /autoindex/i,
53
- // IIS
54
- /\[To Parent Directory\]/i,
55
- /<title>.*- \/<\/title>/i,
56
- // Generic
57
- /Directory listing for/i,
58
- /<pre>.*<a href="[^"]*">[^<]*<\/a>.*<\/pre>/is,
59
- ];
60
-
61
- /**
62
- * Check if HTML content indicates directory listing
63
- */
64
- function isDirectoryListing(html: string): boolean {
65
- return DIRECTORY_LISTING_PATTERNS.some((pattern) => pattern.test(html));
66
- }
67
-
68
- /**
69
- * Detect server type from response
70
- */
71
- function detectServerType(headers: Record<string, string>): string | undefined {
72
- const server = headers.server || headers.Server;
73
- if (server) {
74
- if (/apache/i.test(server)) return 'Apache';
75
- if (/nginx/i.test(server)) return 'Nginx';
76
- if (/iis/i.test(server)) return 'IIS';
77
- if (/cloudflare/i.test(server)) return 'Cloudflare';
78
- return server;
79
- }
80
- return undefined;
81
- }
82
-
83
- /**
84
- * Analyze directory listing security
85
- */
86
- export async function analyzeDirectoryListing(url: string): Promise<{ issues: AuditIssue[]; data: DirectoryListingData }> {
87
- const issues: AuditIssue[] = [];
88
- const baseUrl = new URL(url);
89
- const baseOrigin = baseUrl.origin;
90
-
91
- const vulnerableDirectories: string[] = [];
92
- const checkedDirectories: string[] = [];
93
- let serverType: string | undefined;
94
-
95
- // Check each common directory (parallel with short timeout)
96
- const directoriesToCheck = COMMON_DIRECTORIES.slice(0, 6); // Limit to 6 directories
97
-
98
- // Run all directory checks in parallel with 3s timeout each
99
- const checkPromises = directoriesToCheck.map(async (dir) => {
100
- const dirUrl = `${baseOrigin}${dir}`;
101
- checkedDirectories.push(dir);
102
-
103
- try {
104
- const response = await httpGet<string>(dirUrl, {
105
- timeout: 3000, // Reduced timeout
106
- validateStatus: () => true,
107
- });
108
-
109
- // Get server type from response
110
- if (response.headers) {
111
- const detectedServer = detectServerType(response.headers as Record<string, string>);
112
- if (detectedServer && !serverType) {
113
- serverType = detectedServer;
114
- }
115
- }
116
-
117
- // Check if response indicates directory listing
118
- if (response.status === 200 && typeof response.data === 'string') {
119
- if (isDirectoryListing(response.data)) {
120
- vulnerableDirectories.push(dir);
121
- }
122
- }
123
- } catch {
124
- // Skip failed requests (directory doesn't exist or blocked)
125
- }
126
- });
127
-
128
- // Wait for all checks with overall timeout
129
- await Promise.race([
130
- Promise.all(checkPromises),
131
- new Promise<void>((resolve) => setTimeout(resolve, 8000)), // 8s overall timeout
132
- ]);
133
-
134
- const directoryListingEnabled = vulnerableDirectories.length > 0;
135
-
136
- // Generate issues
137
- if (directoryListingEnabled) {
138
- issues.push({
139
- code: 'DIRECTORY_LISTING_ENABLED',
140
- severity: 'warning',
141
- category: 'security',
142
- title: 'Directory listing enabled',
143
- description: `${vulnerableDirectories.length} director${vulnerableDirectories.length === 1 ? 'y' : 'ies'} expose file listings. This reveals your site structure and potentially sensitive files.`,
144
- impact:
145
- 'Attackers can discover backup files, configuration files, or hidden content. This is a security vulnerability and information disclosure risk.',
146
- howToFix:
147
- serverType === 'Apache'
148
- ? 'Add "Options -Indexes" to your .htaccess file or Apache configuration.'
149
- : serverType === 'Nginx'
150
- ? 'Remove or set "autoindex off" in your Nginx configuration.'
151
- : 'Disable directory listing in your web server configuration. Add index files to directories.',
152
- affectedUrls: vulnerableDirectories.map((dir) => `${baseOrigin}${dir}`),
153
- details: {
154
- vulnerableDirectories,
155
- serverType,
156
- checkedCount: checkedDirectories.length,
157
- recommendation:
158
- 'Ensure all directories have an index.html or index.php, or disable directory listing at the server level.',
159
- },
160
- });
161
- }
162
-
163
- return {
164
- issues,
165
- data: {
166
- directoryListingEnabled,
167
- vulnerableDirectories,
168
- checkedDirectories,
169
- serverType,
170
- },
171
- };
172
- }
@@ -1,191 +0,0 @@
1
- // DOM Analysis Checks
2
- // Checks for DOM size, depth, and render-blocking resources
3
-
4
- import * as cheerio from 'cheerio';
5
- import type { AuditIssue } from '../types.js';
6
- import { ISSUE_DEFINITIONS } from '../types.js';
7
-
8
- const DOM_SIZE_WARNING = 1500;
9
- const DOM_SIZE_ERROR = 3000;
10
- const DOM_DEPTH_WARNING = 32;
11
-
12
- export interface DOMAnalysisData {
13
- nodeCount: number;
14
- maxDepth: number;
15
- renderBlockingResources: {
16
- css: string[];
17
- js: string[];
18
- };
19
- totalRenderBlockingSize: number;
20
- criticalPathLength: number;
21
- }
22
-
23
- /**
24
- * Count total DOM nodes
25
- */
26
- function countNodes($: cheerio.CheerioAPI): number {
27
- let count = 0;
28
-
29
- function traverse(element: unknown): void {
30
- count++;
31
- const el = element as { children?: unknown[] };
32
- if (el.children) {
33
- for (const child of el.children) {
34
- traverse(child);
35
- }
36
- }
37
- }
38
-
39
- const root = $.root();
40
- root.each((_, el) => traverse(el));
41
-
42
- return count;
43
- }
44
-
45
- /**
46
- * Calculate maximum DOM depth
47
- */
48
- function calculateMaxDepth($: cheerio.CheerioAPI): number {
49
- let maxDepth = 0;
50
-
51
- function traverse(element: unknown, depth: number): void {
52
- maxDepth = Math.max(maxDepth, depth);
53
- const el = element as { children?: unknown[] };
54
- if (el.children) {
55
- for (const child of el.children) {
56
- traverse(child, depth + 1);
57
- }
58
- }
59
- }
60
-
61
- const root = $.root();
62
- root.each((_, el) => traverse(el, 0));
63
-
64
- return maxDepth;
65
- }
66
-
67
- /**
68
- * Find render-blocking resources
69
- */
70
- function findRenderBlockingResources($: cheerio.CheerioAPI): { css: string[]; js: string[] } {
71
- const css: string[] = [];
72
- const js: string[] = [];
73
-
74
- // Find render-blocking CSS (in head, without media="print" or disabled)
75
- $('head link[rel="stylesheet"]').each((_, el) => {
76
- const href = $(el).attr('href');
77
- const media = $(el).attr('media');
78
- const disabled = $(el).attr('disabled');
79
-
80
- if (href && media !== 'print' && !disabled) {
81
- css.push(href);
82
- }
83
- });
84
-
85
- // Find inline style tags in head (blocking)
86
- $('head style').each((_, el) => {
87
- const content = $(el).html();
88
- if (content && content.trim().length > 100) {
89
- css.push('[inline-style]');
90
- }
91
- });
92
-
93
- // Find render-blocking JS (in head, without async/defer)
94
- $('head script[src]').each((_, el) => {
95
- const src = $(el).attr('src');
96
- const async = $(el).attr('async');
97
- const defer = $(el).attr('defer');
98
- const type = $(el).attr('type');
99
-
100
- // Skip module scripts (they're deferred by default) and non-JS
101
- if (type === 'module' || (type && !type.includes('javascript'))) {
102
- return;
103
- }
104
-
105
- if (src && !async && !defer) {
106
- js.push(src);
107
- }
108
- });
109
-
110
- return { css, js };
111
- }
112
-
113
- /**
114
- * Analyze DOM structure and render-blocking resources
115
- */
116
- export function analyzeDOMStructure(html: string, url: string): { issues: AuditIssue[]; data: DOMAnalysisData } {
117
- const issues: AuditIssue[] = [];
118
- const $ = cheerio.load(html);
119
-
120
- // Count nodes
121
- const nodeCount = countNodes($);
122
-
123
- // Calculate depth
124
- const maxDepth = calculateMaxDepth($);
125
-
126
- // Find render-blocking resources
127
- const renderBlockingResources = findRenderBlockingResources($);
128
- const criticalPathLength = renderBlockingResources.css.length + renderBlockingResources.js.length;
129
-
130
- // Generate issues
131
- if (nodeCount > DOM_SIZE_ERROR) {
132
- issues.push({
133
- ...ISSUE_DEFINITIONS.DOM_SIZE_EXCESSIVE,
134
- severity: 'error',
135
- affectedUrls: [url],
136
- details: {
137
- nodeCount,
138
- threshold: DOM_SIZE_WARNING,
139
- recommendation: 'Reduce DOM size to under 1500 nodes',
140
- },
141
- });
142
- } else if (nodeCount > DOM_SIZE_WARNING) {
143
- issues.push({
144
- ...ISSUE_DEFINITIONS.DOM_SIZE_EXCESSIVE,
145
- affectedUrls: [url],
146
- details: {
147
- nodeCount,
148
- threshold: DOM_SIZE_WARNING,
149
- recommendation: 'Consider simplifying page structure',
150
- },
151
- });
152
- }
153
-
154
- if (maxDepth > DOM_DEPTH_WARNING) {
155
- issues.push({
156
- ...ISSUE_DEFINITIONS.DOM_DEPTH_EXCESSIVE,
157
- affectedUrls: [url],
158
- details: {
159
- maxDepth,
160
- threshold: DOM_DEPTH_WARNING,
161
- },
162
- });
163
- }
164
-
165
- if (criticalPathLength > 0) {
166
- issues.push({
167
- ...ISSUE_DEFINITIONS.RENDER_BLOCKING_RESOURCES,
168
- affectedUrls: [url],
169
- details: {
170
- cssFiles: renderBlockingResources.css,
171
- jsFiles: renderBlockingResources.js,
172
- totalCount: criticalPathLength,
173
- recommendation:
174
- criticalPathLength > 3
175
- ? 'Inline critical CSS and defer non-critical resources'
176
- : 'Consider using async/defer for scripts',
177
- },
178
- });
179
- }
180
-
181
- return {
182
- issues,
183
- data: {
184
- nodeCount,
185
- maxDepth,
186
- renderBlockingResources,
187
- totalRenderBlockingSize: 0, // Would need to fetch resources to calculate
188
- criticalPathLength,
189
- },
190
- };
191
- }