@rankcli/agent-runtime 0.0.9 → 0.0.11

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 (47) hide show
  1. package/README.md +90 -196
  2. package/dist/analyzer-GMURJADU.mjs +7 -0
  3. package/dist/chunk-2JADKV3Z.mjs +244 -0
  4. package/dist/chunk-3ZSCLNTW.mjs +557 -0
  5. package/dist/chunk-4E4MQOSP.mjs +374 -0
  6. package/dist/chunk-6BWS3CLP.mjs +16 -0
  7. package/dist/chunk-AK2IC22C.mjs +206 -0
  8. package/dist/chunk-K6VSXDD6.mjs +293 -0
  9. package/dist/chunk-M27NQCWW.mjs +303 -0
  10. package/dist/{chunk-YNZYHEYM.mjs → chunk-PJLNXOLN.mjs} +0 -14
  11. package/dist/chunk-VSQD74I7.mjs +474 -0
  12. package/dist/core-web-vitals-analyzer-TE6LQJMS.mjs +7 -0
  13. package/dist/geo-analyzer-D47LTMMA.mjs +25 -0
  14. package/dist/image-optimization-analyzer-XP4OQGRP.mjs +9 -0
  15. package/dist/index.d.mts +612 -17
  16. package/dist/index.d.ts +612 -17
  17. package/dist/index.js +9020 -2686
  18. package/dist/index.mjs +4177 -328
  19. package/dist/internal-linking-analyzer-MRMBV7NM.mjs +9 -0
  20. package/dist/mobile-seo-analyzer-67HNQ7IO.mjs +7 -0
  21. package/dist/security-headers-analyzer-3ZUQARS5.mjs +9 -0
  22. package/dist/structured-data-analyzer-2I4NQAUP.mjs +9 -0
  23. package/package.json +2 -2
  24. package/src/analyzers/core-web-vitals-analyzer.test.ts +236 -0
  25. package/src/analyzers/core-web-vitals-analyzer.ts +557 -0
  26. package/src/analyzers/geo-analyzer.test.ts +310 -0
  27. package/src/analyzers/geo-analyzer.ts +814 -0
  28. package/src/analyzers/image-optimization-analyzer.test.ts +145 -0
  29. package/src/analyzers/image-optimization-analyzer.ts +348 -0
  30. package/src/analyzers/index.ts +233 -0
  31. package/src/analyzers/internal-linking-analyzer.test.ts +141 -0
  32. package/src/analyzers/internal-linking-analyzer.ts +419 -0
  33. package/src/analyzers/mobile-seo-analyzer.test.ts +140 -0
  34. package/src/analyzers/mobile-seo-analyzer.ts +455 -0
  35. package/src/analyzers/security-headers-analyzer.test.ts +115 -0
  36. package/src/analyzers/security-headers-analyzer.ts +318 -0
  37. package/src/analyzers/structured-data-analyzer.test.ts +210 -0
  38. package/src/analyzers/structured-data-analyzer.ts +590 -0
  39. package/src/audit/engine.ts +3 -3
  40. package/src/audit/types.ts +3 -2
  41. package/src/fixer/framework-fixes.test.ts +489 -0
  42. package/src/fixer/framework-fixes.ts +3418 -0
  43. package/src/frameworks/detector.ts +642 -114
  44. package/src/frameworks/suggestion-engine.ts +38 -1
  45. package/src/index.ts +3 -0
  46. package/src/types.ts +15 -1
  47. package/dist/analyzer-2CSWIQGD.mjs +0 -6
@@ -0,0 +1,310 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ analyzeRobotsTxtForAI,
4
+ detectRenderingMode,
5
+ analyzeContentStructure,
6
+ analyzeCitationReadiness,
7
+ analyzeGEO,
8
+ generateAIFriendlyRobotsTxt,
9
+ } from './geo-analyzer.js';
10
+
11
+ describe('GEO Analyzer', () => {
12
+ describe('analyzeRobotsTxtForAI', () => {
13
+ it('detects blocked AI crawlers', () => {
14
+ const robotsTxt = `
15
+ User-agent: GPTBot
16
+ Disallow: /
17
+
18
+ User-agent: PerplexityBot
19
+ Disallow: /
20
+ `;
21
+ const result = analyzeRobotsTxtForAI(robotsTxt);
22
+ expect(result.blocked).toContain('GPTBot');
23
+ expect(result.blocked).toContain('PerplexityBot');
24
+ expect(result.recommendations.length).toBeGreaterThan(0);
25
+ });
26
+
27
+ it('detects allowed AI crawlers', () => {
28
+ const robotsTxt = `
29
+ User-agent: *
30
+ Allow: /
31
+ `;
32
+ const result = analyzeRobotsTxtForAI(robotsTxt);
33
+ expect(result.allowed).toContain('GPTBot');
34
+ expect(result.allowed).toContain('ClaudeBot');
35
+ expect(result.blocked.length).toBe(0);
36
+ });
37
+
38
+ it('handles wildcard disallow', () => {
39
+ const robotsTxt = `
40
+ User-agent: *
41
+ Disallow: /
42
+ `;
43
+ const result = analyzeRobotsTxtForAI(robotsTxt);
44
+ expect(result.blocked.length).toBeGreaterThan(0);
45
+ });
46
+ });
47
+
48
+ describe('detectRenderingMode', () => {
49
+ it('detects SSR with Next.js', () => {
50
+ // Need substantial content (>2 paragraphs or >2 headings) for contentInHTML=true
51
+ const html = `
52
+ <!DOCTYPE html>
53
+ <html>
54
+ <head><title>Test</title></head>
55
+ <body>
56
+ <div id="__next">
57
+ <h1>Hello World</h1>
58
+ <h2>Section One</h2>
59
+ <p>This is content paragraph one.</p>
60
+ <h2>Section Two</h2>
61
+ <p>This is content paragraph two.</p>
62
+ <h2>Section Three</h2>
63
+ <p>This is content paragraph three.</p>
64
+ </div>
65
+ <script id="__NEXT_DATA__" type="application/json">{"props":{}}</script>
66
+ </body>
67
+ </html>
68
+ `;
69
+ const result = detectRenderingMode(html);
70
+ expect(result.serverSideRendered).toBe(true);
71
+ expect(result.contentInHTML).toBe(true);
72
+ });
73
+
74
+ it('detects client-side React app', () => {
75
+ const html = `
76
+ <!DOCTYPE html>
77
+ <html>
78
+ <head><title>Test</title></head>
79
+ <body>
80
+ <div id="root" data-reactroot></div>
81
+ <script src="/bundle.js"></script>
82
+ </body>
83
+ </html>
84
+ `;
85
+ const result = detectRenderingMode(html);
86
+ expect(result.jsRenderingRequired).toBe(true);
87
+ });
88
+
89
+ it('detects substantial content', () => {
90
+ const html = `
91
+ <!DOCTYPE html>
92
+ <html>
93
+ <head><title>Test</title></head>
94
+ <body>
95
+ <h1>Main Title</h1>
96
+ <h2>Section 1</h2>
97
+ <p>Some content here</p>
98
+ <h2>Section 2</h2>
99
+ <p>More content</p>
100
+ <h3>Subsection</h3>
101
+ <p>Even more content</p>
102
+ </body>
103
+ </html>
104
+ `;
105
+ const result = detectRenderingMode(html);
106
+ expect(result.contentInHTML).toBe(true);
107
+ expect(result.serverSideRendered).toBe(true);
108
+ });
109
+ });
110
+
111
+ describe('analyzeContentStructure', () => {
112
+ it('detects JSON-LD structured data', () => {
113
+ const html = `
114
+ <!DOCTYPE html>
115
+ <html>
116
+ <head>
117
+ <script type="application/ld+json">
118
+ {"@type": "Article", "headline": "Test"}
119
+ </script>
120
+ </head>
121
+ <body><h1>Test</h1></body>
122
+ </html>
123
+ `;
124
+ const result = analyzeContentStructure(html);
125
+ expect(result.hasStructuredData).toBe(true);
126
+ expect(result.hasArticleSchema).toBe(true);
127
+ });
128
+
129
+ it('detects FAQ schema', () => {
130
+ const html = `
131
+ <!DOCTYPE html>
132
+ <html>
133
+ <head>
134
+ <script type="application/ld+json">
135
+ {"@type": "FAQPage", "mainEntity": []}
136
+ </script>
137
+ </head>
138
+ <body><h1>FAQ</h1></body>
139
+ </html>
140
+ `;
141
+ const result = analyzeContentStructure(html);
142
+ expect(result.hasFAQSchema).toBe(true);
143
+ });
144
+
145
+ it('analyzes heading hierarchy', () => {
146
+ const html = `
147
+ <html><body>
148
+ <h1>Main</h1>
149
+ <h2>Sub 1</h2>
150
+ <h2>Sub 2</h2>
151
+ <h3>Detail</h3>
152
+ </body></html>
153
+ `;
154
+ const result = analyzeContentStructure(html);
155
+ expect(result.headingHierarchy).toBe('good');
156
+ });
157
+
158
+ it('detects poor heading hierarchy', () => {
159
+ const html = `<html><body><h3>Random</h3><h5>No structure</h5></body></html>`;
160
+ const result = analyzeContentStructure(html);
161
+ expect(result.headingHierarchy).toBe('poor');
162
+ });
163
+
164
+ it('counts lists and tables', () => {
165
+ const html = `
166
+ <html><body>
167
+ <ul><li>Item 1</li><li>Item 2</li></ul>
168
+ <ol><li>First</li></ol>
169
+ <table><tr><td>Data</td></tr></table>
170
+ </body></html>
171
+ `;
172
+ const result = analyzeContentStructure(html);
173
+ expect(result.listsAndTables).toBe(3);
174
+ });
175
+ });
176
+
177
+ describe('analyzeCitationReadiness', () => {
178
+ it('detects citations and references', () => {
179
+ const html = `
180
+ <html><body>
181
+ <p>According to research [1], this is true.</p>
182
+ <cite>Smith et al., 2023</cite>
183
+ <blockquote>An important quote</blockquote>
184
+ </body></html>
185
+ `;
186
+ const result = analyzeCitationReadiness(html);
187
+ expect(result.hasCitations).toBe(true);
188
+ expect(result.hasQuotes).toBe(true);
189
+ });
190
+
191
+ it('detects statistics', () => {
192
+ const html = `<html><body><p>Revenue increased by 50% in 2023.</p></body></html>`;
193
+ const result = analyzeCitationReadiness(html);
194
+ expect(result.hasStatistics).toBe(true);
195
+ });
196
+
197
+ it('detects author info', () => {
198
+ const html = `
199
+ <html>
200
+ <head><meta name="author" content="John Doe"></head>
201
+ <body><p class="author">Written by John</p></body>
202
+ </html>
203
+ `;
204
+ const result = analyzeCitationReadiness(html);
205
+ expect(result.hasAuthorInfo).toBe(true);
206
+ });
207
+
208
+ it('detects publish dates', () => {
209
+ const html = `
210
+ <html><body>
211
+ <time datetime="2024-01-15">Jan 15, 2024</time>
212
+ </body></html>
213
+ `;
214
+ const result = analyzeCitationReadiness(html);
215
+ expect(result.hasPublishDate).toBe(true);
216
+ });
217
+
218
+ it('collects trust signals', () => {
219
+ const html = `
220
+ <html>
221
+ <head><meta name="author" content="Expert"></head>
222
+ <body>
223
+ <cite>Source</cite>
224
+ <p>Studies show 80% improvement</p>
225
+ <time datetime="2024-01-01">Published</time>
226
+ </body></html>
227
+ `;
228
+ const result = analyzeCitationReadiness(html);
229
+ expect(result.trustSignals.length).toBeGreaterThanOrEqual(3);
230
+ });
231
+ });
232
+
233
+ describe('analyzeGEO', () => {
234
+ it('returns comprehensive GEO analysis', async () => {
235
+ const html = `
236
+ <!DOCTYPE html>
237
+ <html>
238
+ <head>
239
+ <script type="application/ld+json">{"@type": "Article", "headline": "Test"}</script>
240
+ </head>
241
+ <body>
242
+ <h1>Test Article</h1>
243
+ <h2>Section</h2>
244
+ <p>Content with statistics: 50% increase.</p>
245
+ <ul><li>Point 1</li><li>Point 2</li></ul>
246
+ <cite>Source reference</cite>
247
+ </body>
248
+ </html>
249
+ `;
250
+ const result = await analyzeGEO(html, 'https://example.com/article');
251
+
252
+ expect(result.score).toBeGreaterThan(0);
253
+ expect(result.aiCrawlerAccess).toBeDefined();
254
+ expect(result.contentStructure).toBeDefined();
255
+ expect(result.citationReadiness).toBeDefined();
256
+ expect(result.llmSignals).toBeDefined();
257
+ expect(result.recommendations).toBeDefined();
258
+ });
259
+
260
+ it('penalizes JS-only sites', async () => {
261
+ const spaHtml = `
262
+ <!DOCTYPE html>
263
+ <html>
264
+ <head><title>SPA</title></head>
265
+ <body><div id="root"></div><script src="/app.js"></script></body>
266
+ </html>
267
+ `;
268
+ const ssrHtml = `
269
+ <!DOCTYPE html>
270
+ <html>
271
+ <head><title>SSR</title></head>
272
+ <body>
273
+ <h1>Real Content</h1>
274
+ <p>Lots of text here that crawlers can read.</p>
275
+ <h2>Section</h2>
276
+ <p>More content</p>
277
+ </body>
278
+ </html>
279
+ `;
280
+ const spaResult = await analyzeGEO(spaHtml, 'https://example.com');
281
+ const ssrResult = await analyzeGEO(ssrHtml, 'https://example.com');
282
+
283
+ expect(ssrResult.score).toBeGreaterThan(spaResult.score);
284
+ });
285
+
286
+ it('includes blocked crawlers in issues', async () => {
287
+ const html = `<html><body><h1>Test</h1><p>Content</p></body></html>`;
288
+ const robotsTxt = `
289
+ User-agent: GPTBot
290
+ Disallow: /
291
+ `;
292
+ const result = await analyzeGEO(html, 'https://example.com', robotsTxt);
293
+
294
+ expect(result.aiCrawlerAccess.blockedCrawlers).toContain('GPTBot');
295
+ expect(result.issues.some(i => i.code === 'GEO_AI_CRAWLERS_BLOCKED')).toBe(true);
296
+ });
297
+ });
298
+
299
+ describe('generateAIFriendlyRobotsTxt', () => {
300
+ it('generates robots.txt with AI crawler rules', () => {
301
+ const result = generateAIFriendlyRobotsTxt('https://example.com');
302
+
303
+ expect(result).toContain('GPTBot');
304
+ expect(result).toContain('Claude-Web');
305
+ expect(result).toContain('PerplexityBot');
306
+ expect(result).toContain('Allow: /');
307
+ expect(result).toContain('Sitemap: https://example.com/sitemap.xml');
308
+ });
309
+ });
310
+ });