@rankcli/agent-runtime 0.0.14 → 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 (199) hide show
  1. package/dist/index.d.mts +77 -393
  2. package/dist/index.d.ts +77 -393
  3. package/dist/index.js +417 -6443
  4. package/dist/index.mjs +296 -6313
  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/ai-citation-worthiness.ts +0 -399
  28. package/src/audit/checks/ai-content-structure.ts +0 -325
  29. package/src/audit/checks/ai-readiness.ts +0 -339
  30. package/src/audit/checks/anchor-text.ts +0 -179
  31. package/src/audit/checks/answer-conciseness.ts +0 -322
  32. package/src/audit/checks/asset-minification.ts +0 -270
  33. package/src/audit/checks/bing-optimization.ts +0 -206
  34. package/src/audit/checks/brand-mention-optimization.ts +0 -349
  35. package/src/audit/checks/caching-headers.ts +0 -305
  36. package/src/audit/checks/canonical-advanced.ts +0 -150
  37. package/src/audit/checks/canonical-domain.ts +0 -196
  38. package/src/audit/checks/citation-quality.ts +0 -358
  39. package/src/audit/checks/client-rendering.ts +0 -542
  40. package/src/audit/checks/color-contrast.ts +0 -342
  41. package/src/audit/checks/content-freshness.ts +0 -170
  42. package/src/audit/checks/content-science.ts +0 -589
  43. package/src/audit/checks/conversion-elements.ts +0 -526
  44. package/src/audit/checks/crawlability.ts +0 -220
  45. package/src/audit/checks/directory-listing.ts +0 -172
  46. package/src/audit/checks/dom-analysis.ts +0 -191
  47. package/src/audit/checks/dom-size.ts +0 -246
  48. package/src/audit/checks/duplicate-content.ts +0 -194
  49. package/src/audit/checks/eeat-signals.ts +0 -990
  50. package/src/audit/checks/entity-seo.ts +0 -396
  51. package/src/audit/checks/featured-snippet.ts +0 -473
  52. package/src/audit/checks/freshness-signals.ts +0 -443
  53. package/src/audit/checks/funnel-intent.ts +0 -463
  54. package/src/audit/checks/hreflang.ts +0 -174
  55. package/src/audit/checks/html-compliance.ts +0 -302
  56. package/src/audit/checks/image-dimensions.ts +0 -167
  57. package/src/audit/checks/images.ts +0 -160
  58. package/src/audit/checks/indexnow.ts +0 -275
  59. package/src/audit/checks/interactive-tools.ts +0 -475
  60. package/src/audit/checks/internal-link-graph.ts +0 -436
  61. package/src/audit/checks/keyword-analysis.ts +0 -239
  62. package/src/audit/checks/keyword-cannibalization.ts +0 -385
  63. package/src/audit/checks/keyword-placement.ts +0 -471
  64. package/src/audit/checks/links.ts +0 -273
  65. package/src/audit/checks/llms-txt.ts +0 -224
  66. package/src/audit/checks/local-seo.ts +0 -296
  67. package/src/audit/checks/mobile.ts +0 -167
  68. package/src/audit/checks/modern-images.ts +0 -226
  69. package/src/audit/checks/navboost-signals.ts +0 -395
  70. package/src/audit/checks/on-page.ts +0 -209
  71. package/src/audit/checks/page-resources.ts +0 -285
  72. package/src/audit/checks/pagination.ts +0 -180
  73. package/src/audit/checks/performance.ts +0 -153
  74. package/src/audit/checks/platform-presence.ts +0 -580
  75. package/src/audit/checks/redirect-analysis.ts +0 -153
  76. package/src/audit/checks/redirect-chain.ts +0 -389
  77. package/src/audit/checks/resource-hints.ts +0 -420
  78. package/src/audit/checks/responsive-css.ts +0 -247
  79. package/src/audit/checks/responsive-images.ts +0 -396
  80. package/src/audit/checks/review-ecosystem.ts +0 -415
  81. package/src/audit/checks/robots-validation.ts +0 -373
  82. package/src/audit/checks/security-headers.ts +0 -188
  83. package/src/audit/checks/security.ts +0 -144
  84. package/src/audit/checks/serp-preview.ts +0 -251
  85. package/src/audit/checks/site-maturity.ts +0 -453
  86. package/src/audit/checks/social-meta.test.ts +0 -275
  87. package/src/audit/checks/social-meta.ts +0 -134
  88. package/src/audit/checks/soft-404.ts +0 -151
  89. package/src/audit/checks/structured-data.ts +0 -238
  90. package/src/audit/checks/tech-detection.ts +0 -496
  91. package/src/audit/checks/topical-clusters.ts +0 -435
  92. package/src/audit/checks/tracker-bloat.ts +0 -462
  93. package/src/audit/checks/tracking-verification.test.ts +0 -371
  94. package/src/audit/checks/tracking-verification.ts +0 -636
  95. package/src/audit/checks/url-safety.ts +0 -682
  96. package/src/audit/deno-entry.ts +0 -66
  97. package/src/audit/discovery/index.ts +0 -15
  98. package/src/audit/discovery/link-crawler.ts +0 -233
  99. package/src/audit/discovery/repo-routes.ts +0 -347
  100. package/src/audit/engine.ts +0 -627
  101. package/src/audit/fixes/index.ts +0 -209
  102. package/src/audit/fixes/social-meta-fixes.test.ts +0 -329
  103. package/src/audit/fixes/social-meta-fixes.ts +0 -463
  104. package/src/audit/index.ts +0 -74
  105. package/src/audit/runner.test.ts +0 -299
  106. package/src/audit/runner.ts +0 -130
  107. package/src/audit/types.ts +0 -1963
  108. package/src/content/featured-snippet.ts +0 -367
  109. package/src/content/generator.test.ts +0 -534
  110. package/src/content/generator.ts +0 -501
  111. package/src/content/headline.ts +0 -317
  112. package/src/content/index.ts +0 -62
  113. package/src/content/intent.ts +0 -258
  114. package/src/content/keyword-density.ts +0 -349
  115. package/src/content/readability.ts +0 -262
  116. package/src/executor.ts +0 -336
  117. package/src/fixer/framework-fixes.test.ts +0 -489
  118. package/src/fixer/framework-fixes.ts +0 -5287
  119. package/src/fixer/index.ts +0 -6
  120. package/src/fixer/schemas.ts +0 -971
  121. package/src/fixer.ts +0 -642
  122. package/src/frameworks/detector.test.ts +0 -248
  123. package/src/frameworks/detector.ts +0 -899
  124. package/src/frameworks/index.ts +0 -68
  125. package/src/frameworks/recipes/angular.yaml +0 -171
  126. package/src/frameworks/recipes/astro.yaml +0 -206
  127. package/src/frameworks/recipes/django.yaml +0 -180
  128. package/src/frameworks/recipes/laravel.yaml +0 -137
  129. package/src/frameworks/recipes/nextjs.yaml +0 -268
  130. package/src/frameworks/recipes/nuxt.yaml +0 -175
  131. package/src/frameworks/recipes/rails.yaml +0 -188
  132. package/src/frameworks/recipes/react.yaml +0 -202
  133. package/src/frameworks/recipes/sveltekit.yaml +0 -154
  134. package/src/frameworks/recipes/vue.yaml +0 -137
  135. package/src/frameworks/recipes/wordpress.yaml +0 -209
  136. package/src/frameworks/suggestion-engine.ts +0 -357
  137. package/src/geo/geo-content.test.ts +0 -305
  138. package/src/geo/geo-content.ts +0 -266
  139. package/src/geo/geo-history.test.ts +0 -473
  140. package/src/geo/geo-history.ts +0 -433
  141. package/src/geo/geo-tracker.test.ts +0 -359
  142. package/src/geo/geo-tracker.ts +0 -411
  143. package/src/geo/index.ts +0 -11
  144. package/src/geo/llm-citation-checker.ts +0 -188
  145. package/src/git/commit-helper.test.ts +0 -261
  146. package/src/git/commit-helper.ts +0 -329
  147. package/src/git/index.ts +0 -12
  148. package/src/git/pr-helper.test.ts +0 -284
  149. package/src/git/pr-helper.ts +0 -307
  150. package/src/index.ts +0 -76
  151. package/src/keywords/ai-keyword-engine.ts +0 -1062
  152. package/src/keywords/ai-summarizer.ts +0 -387
  153. package/src/keywords/ci-mode.ts +0 -555
  154. package/src/keywords/engine.ts +0 -359
  155. package/src/keywords/index.ts +0 -151
  156. package/src/keywords/llm-judge.ts +0 -357
  157. package/src/keywords/nlp-analysis.ts +0 -706
  158. package/src/keywords/prioritizer.ts +0 -295
  159. package/src/keywords/site-crawler.ts +0 -342
  160. package/src/keywords/sources/autocomplete.ts +0 -139
  161. package/src/keywords/sources/competitive-search.ts +0 -450
  162. package/src/keywords/sources/competitor-analysis.ts +0 -374
  163. package/src/keywords/sources/dataforseo.ts +0 -206
  164. package/src/keywords/sources/free-sources.ts +0 -294
  165. package/src/keywords/sources/gsc.ts +0 -123
  166. package/src/keywords/topic-grouping.ts +0 -327
  167. package/src/keywords/types.ts +0 -144
  168. package/src/keywords/wizard.ts +0 -457
  169. package/src/loader.ts +0 -40
  170. package/src/ranking/index.ts +0 -5
  171. package/src/ranking/serp-client.ts +0 -348
  172. package/src/ranking/tracker.ts +0 -380
  173. package/src/ranking/types.ts +0 -123
  174. package/src/reports/index.ts +0 -7
  175. package/src/reports/report-generator.test.ts +0 -293
  176. package/src/reports/report-generator.ts +0 -713
  177. package/src/scheduler/alerts.test.ts +0 -458
  178. package/src/scheduler/alerts.ts +0 -328
  179. package/src/scheduler/index.ts +0 -8
  180. package/src/scheduler/scheduled-audit.test.ts +0 -377
  181. package/src/scheduler/scheduled-audit.ts +0 -149
  182. package/src/test/integration-test.ts +0 -325
  183. package/src/tools/analyzer.ts +0 -373
  184. package/src/tools/crawl.ts +0 -293
  185. package/src/tools/files.ts +0 -301
  186. package/src/tools/h1-fixer.ts +0 -249
  187. package/src/tools/index.ts +0 -67
  188. package/src/tracking/github-action.ts +0 -326
  189. package/src/tracking/google-analytics.ts +0 -265
  190. package/src/tracking/index.ts +0 -45
  191. package/src/tracking/report-generator.ts +0 -386
  192. package/src/tracking/search-console.ts +0 -335
  193. package/src/types.ts +0 -148
  194. package/src/utils/http.ts +0 -302
  195. package/src/wasm-adapter.ts +0 -297
  196. package/src/wasm-entry.ts +0 -14
  197. package/tsconfig.json +0 -17
  198. package/tsup.wasm.config.ts +0 -26
  199. 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
- }