@rankcli/agent-runtime 0.0.8 → 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.
- package/README.md +90 -196
- package/dist/analyzer-GMURJADU.mjs +7 -0
- package/dist/chunk-2JADKV3Z.mjs +244 -0
- package/dist/chunk-3ZSCLNTW.mjs +557 -0
- package/dist/chunk-4E4MQOSP.mjs +374 -0
- package/dist/chunk-6BWS3CLP.mjs +16 -0
- package/dist/chunk-AK2IC22C.mjs +206 -0
- package/dist/chunk-K6VSXDD6.mjs +293 -0
- package/dist/chunk-M27NQCWW.mjs +303 -0
- package/dist/{chunk-YNZYHEYM.mjs → chunk-PJLNXOLN.mjs} +0 -14
- package/dist/chunk-VSQD74I7.mjs +474 -0
- package/dist/core-web-vitals-analyzer-TE6LQJMS.mjs +7 -0
- package/dist/geo-analyzer-D47LTMMA.mjs +25 -0
- package/dist/image-optimization-analyzer-XP4OQGRP.mjs +9 -0
- package/dist/index.d.mts +1523 -17
- package/dist/index.d.ts +1523 -17
- package/dist/index.js +9582 -2664
- package/dist/index.mjs +4812 -380
- package/dist/internal-linking-analyzer-MRMBV7NM.mjs +9 -0
- package/dist/mobile-seo-analyzer-67HNQ7IO.mjs +7 -0
- package/dist/security-headers-analyzer-3ZUQARS5.mjs +9 -0
- package/dist/structured-data-analyzer-2I4NQAUP.mjs +9 -0
- package/package.json +2 -2
- package/src/analyzers/core-web-vitals-analyzer.test.ts +236 -0
- package/src/analyzers/core-web-vitals-analyzer.ts +557 -0
- package/src/analyzers/geo-analyzer.test.ts +310 -0
- package/src/analyzers/geo-analyzer.ts +814 -0
- package/src/analyzers/image-optimization-analyzer.test.ts +145 -0
- package/src/analyzers/image-optimization-analyzer.ts +348 -0
- package/src/analyzers/index.ts +233 -0
- package/src/analyzers/internal-linking-analyzer.test.ts +141 -0
- package/src/analyzers/internal-linking-analyzer.ts +419 -0
- package/src/analyzers/mobile-seo-analyzer.test.ts +140 -0
- package/src/analyzers/mobile-seo-analyzer.ts +455 -0
- package/src/analyzers/security-headers-analyzer.test.ts +115 -0
- package/src/analyzers/security-headers-analyzer.ts +318 -0
- package/src/analyzers/structured-data-analyzer.test.ts +210 -0
- package/src/analyzers/structured-data-analyzer.ts +590 -0
- package/src/audit/engine.ts +3 -3
- package/src/audit/types.ts +3 -2
- package/src/fixer/framework-fixes.test.ts +489 -0
- package/src/fixer/framework-fixes.ts +3418 -0
- package/src/fixer/index.ts +1 -0
- package/src/fixer/schemas.ts +971 -0
- package/src/frameworks/detector.ts +642 -114
- package/src/frameworks/suggestion-engine.ts +38 -1
- package/src/index.ts +6 -0
- package/src/types.ts +15 -1
- package/dist/analyzer-2CSWIQGD.mjs +0 -6
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RankCLI Analyzers
|
|
3
|
+
*
|
|
4
|
+
* Industry-leading SEO analysis suite.
|
|
5
|
+
* Includes traditional SEO, Core Web Vitals, GEO (AI Search), and more.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// GEO (Generative Engine Optimization) - AI Search
|
|
9
|
+
export {
|
|
10
|
+
analyzeGEO,
|
|
11
|
+
analyzeRobotsTxtForAI,
|
|
12
|
+
detectRenderingMode,
|
|
13
|
+
analyzeContentStructure,
|
|
14
|
+
analyzeCitationReadiness,
|
|
15
|
+
analyzeEntityExtraction,
|
|
16
|
+
calculateLLMSignals,
|
|
17
|
+
generateAIFriendlyRobotsTxt,
|
|
18
|
+
AI_CRAWLERS_INFO,
|
|
19
|
+
type GEOAnalysisResult,
|
|
20
|
+
type AIAccessResult,
|
|
21
|
+
type ContentStructureResult,
|
|
22
|
+
type CitationResult,
|
|
23
|
+
type EntityResult,
|
|
24
|
+
type LLMSignalResult,
|
|
25
|
+
} from './geo-analyzer.js';
|
|
26
|
+
|
|
27
|
+
// Core Web Vitals
|
|
28
|
+
export {
|
|
29
|
+
analyzeCoreWebVitals,
|
|
30
|
+
type CoreWebVitalsEstimate,
|
|
31
|
+
} from './core-web-vitals-analyzer.js';
|
|
32
|
+
|
|
33
|
+
// Security Headers
|
|
34
|
+
export {
|
|
35
|
+
analyzeSecurityHeaders,
|
|
36
|
+
generateSecurityHeaders,
|
|
37
|
+
type SecurityHeadersResult,
|
|
38
|
+
} from './security-headers-analyzer.js';
|
|
39
|
+
|
|
40
|
+
// Structured Data / Schema.org
|
|
41
|
+
export {
|
|
42
|
+
analyzeStructuredData,
|
|
43
|
+
generateSchemaTemplate,
|
|
44
|
+
type StructuredDataResult,
|
|
45
|
+
type SchemaItem,
|
|
46
|
+
} from './structured-data-analyzer.js';
|
|
47
|
+
|
|
48
|
+
// Image Optimization
|
|
49
|
+
export {
|
|
50
|
+
analyzeImages,
|
|
51
|
+
generateResponsiveImage,
|
|
52
|
+
type ImageAnalysisResult,
|
|
53
|
+
type ImageInfo,
|
|
54
|
+
} from './image-optimization-analyzer.js';
|
|
55
|
+
|
|
56
|
+
// Internal Linking
|
|
57
|
+
export {
|
|
58
|
+
analyzeInternalLinking,
|
|
59
|
+
suggestInternalLinks,
|
|
60
|
+
type InternalLinkingResult,
|
|
61
|
+
type LinkInfo,
|
|
62
|
+
type AnchorTextStats,
|
|
63
|
+
} from './internal-linking-analyzer.js';
|
|
64
|
+
|
|
65
|
+
// Mobile SEO
|
|
66
|
+
export {
|
|
67
|
+
analyzeMobileSEO,
|
|
68
|
+
type MobileSEOResult,
|
|
69
|
+
type ViewportAnalysis,
|
|
70
|
+
type TouchTargetAnalysis,
|
|
71
|
+
type FontSizeAnalysis,
|
|
72
|
+
type ContentWidthAnalysis,
|
|
73
|
+
type MobileSpecificAnalysis,
|
|
74
|
+
} from './mobile-seo-analyzer.js';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Comprehensive SEO Analysis
|
|
78
|
+
*
|
|
79
|
+
* Runs all analyzers and produces a unified report.
|
|
80
|
+
*/
|
|
81
|
+
export interface ComprehensiveSEOResult {
|
|
82
|
+
url: string;
|
|
83
|
+
timestamp: string;
|
|
84
|
+
overallScore: number;
|
|
85
|
+
grades: {
|
|
86
|
+
geo: number;
|
|
87
|
+
coreWebVitals: number;
|
|
88
|
+
security: string;
|
|
89
|
+
structuredData: number;
|
|
90
|
+
images: number;
|
|
91
|
+
internalLinking: number;
|
|
92
|
+
mobile: number;
|
|
93
|
+
};
|
|
94
|
+
geo: import('./geo-analyzer.js').GEOAnalysisResult;
|
|
95
|
+
coreWebVitals: import('./core-web-vitals-analyzer.js').CoreWebVitalsEstimate;
|
|
96
|
+
security: import('./security-headers-analyzer.js').SecurityHeadersResult;
|
|
97
|
+
structuredData: import('./structured-data-analyzer.js').StructuredDataResult;
|
|
98
|
+
images: import('./image-optimization-analyzer.js').ImageAnalysisResult;
|
|
99
|
+
internalLinking: import('./internal-linking-analyzer.js').InternalLinkingResult;
|
|
100
|
+
mobile: import('./mobile-seo-analyzer.js').MobileSEOResult;
|
|
101
|
+
allIssues: import('../audit/types.js').AuditIssue[];
|
|
102
|
+
prioritizedRecommendations: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run comprehensive SEO analysis
|
|
107
|
+
*/
|
|
108
|
+
export async function analyzeComprehensive(
|
|
109
|
+
html: string,
|
|
110
|
+
url: string,
|
|
111
|
+
options: {
|
|
112
|
+
robotsTxt?: string;
|
|
113
|
+
headers?: Record<string, string>;
|
|
114
|
+
} = {}
|
|
115
|
+
): Promise<ComprehensiveSEOResult> {
|
|
116
|
+
const { robotsTxt, headers } = options;
|
|
117
|
+
|
|
118
|
+
// Import analyzers
|
|
119
|
+
const { analyzeGEO } = await import('./geo-analyzer.js');
|
|
120
|
+
const { analyzeCoreWebVitals } = await import('./core-web-vitals-analyzer.js');
|
|
121
|
+
const { analyzeSecurityHeaders } = await import('./security-headers-analyzer.js');
|
|
122
|
+
const { analyzeStructuredData } = await import('./structured-data-analyzer.js');
|
|
123
|
+
const { analyzeImages } = await import('./image-optimization-analyzer.js');
|
|
124
|
+
const { analyzeInternalLinking } = await import('./internal-linking-analyzer.js');
|
|
125
|
+
const { analyzeMobileSEO } = await import('./mobile-seo-analyzer.js');
|
|
126
|
+
|
|
127
|
+
// Run all analyzers
|
|
128
|
+
const geo = await analyzeGEO(html, url, robotsTxt);
|
|
129
|
+
const coreWebVitals = analyzeCoreWebVitals(html, url, headers);
|
|
130
|
+
const security = analyzeSecurityHeaders(headers || {}, url);
|
|
131
|
+
const structuredData = analyzeStructuredData(html, url);
|
|
132
|
+
const images = analyzeImages(html, url);
|
|
133
|
+
const internalLinking = analyzeInternalLinking(html, url);
|
|
134
|
+
const mobile = analyzeMobileSEO(html, url);
|
|
135
|
+
|
|
136
|
+
// Collect all issues
|
|
137
|
+
const allIssues = [
|
|
138
|
+
...geo.issues,
|
|
139
|
+
...coreWebVitals.issues,
|
|
140
|
+
...security.issues,
|
|
141
|
+
...structuredData.issues,
|
|
142
|
+
...images.issues,
|
|
143
|
+
...internalLinking.issues,
|
|
144
|
+
...mobile.issues,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
// Sort by severity
|
|
148
|
+
const severityOrder: Record<string, number> = { critical: 0, error: 1, warning: 2, info: 3, notice: 4 };
|
|
149
|
+
allIssues.sort((a, b) => (severityOrder[a.severity] ?? 5) - (severityOrder[b.severity] ?? 5));
|
|
150
|
+
|
|
151
|
+
// Calculate overall score (weighted average)
|
|
152
|
+
const weights = {
|
|
153
|
+
geo: 0.20, // AI search is crucial
|
|
154
|
+
cwv: 0.20, // Core Web Vitals
|
|
155
|
+
security: 0.10, // Security headers
|
|
156
|
+
schema: 0.15, // Structured data
|
|
157
|
+
images: 0.10, // Image optimization
|
|
158
|
+
links: 0.10, // Internal linking
|
|
159
|
+
mobile: 0.15, // Mobile SEO
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const securityScore =
|
|
163
|
+
security.grade === 'A+' ? 100 :
|
|
164
|
+
security.grade === 'A' ? 90 :
|
|
165
|
+
security.grade === 'B' ? 75 :
|
|
166
|
+
security.grade === 'C' ? 55 :
|
|
167
|
+
security.grade === 'D' ? 35 : 20;
|
|
168
|
+
|
|
169
|
+
const overallScore = Math.round(
|
|
170
|
+
(geo.score * weights.geo) +
|
|
171
|
+
(coreWebVitals.overallScore * weights.cwv) +
|
|
172
|
+
(securityScore * weights.security) +
|
|
173
|
+
(structuredData.score * weights.schema) +
|
|
174
|
+
(images.score * weights.images) +
|
|
175
|
+
(internalLinking.score * weights.links) +
|
|
176
|
+
(mobile.score * weights.mobile)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Prioritize recommendations
|
|
180
|
+
const prioritizedRecommendations: string[] = [];
|
|
181
|
+
|
|
182
|
+
// Critical GEO issues first
|
|
183
|
+
if (geo.aiCrawlerAccess.blockedCrawlers.length > 0) {
|
|
184
|
+
prioritizedRecommendations.push('🚨 URGENT: Unblock AI crawlers (GPTBot, PerplexityBot) in robots.txt');
|
|
185
|
+
}
|
|
186
|
+
if (geo.aiCrawlerAccess.jsRenderingRequired && !geo.aiCrawlerAccess.hasPrerendering) {
|
|
187
|
+
prioritizedRecommendations.push('🚨 URGENT: Implement SSR - AI crawlers cannot see your JS-rendered content');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// CWV critical issues
|
|
191
|
+
const criticalCWV = coreWebVitals.issues.filter(i => i.severity === 'critical');
|
|
192
|
+
criticalCWV.forEach(i => prioritizedRecommendations.push(`⚡ ${i.title}`));
|
|
193
|
+
|
|
194
|
+
// Security critical
|
|
195
|
+
if (!security.https.enabled) {
|
|
196
|
+
prioritizedRecommendations.push('🔒 Enable HTTPS immediately');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Mobile critical
|
|
200
|
+
if (!mobile.viewport.hasViewport) {
|
|
201
|
+
prioritizedRecommendations.push('📱 Add viewport meta tag for mobile rendering');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Add top recommendations from each analyzer
|
|
205
|
+
geo.recommendations.slice(0, 2).forEach(r => prioritizedRecommendations.push(r));
|
|
206
|
+
structuredData.recommendations.slice(0, 2).forEach(r => prioritizedRecommendations.push(r));
|
|
207
|
+
images.recommendations.slice(0, 2).forEach(r => prioritizedRecommendations.push(r));
|
|
208
|
+
internalLinking.recommendations.slice(0, 2).forEach(r => prioritizedRecommendations.push(r));
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
url,
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
overallScore,
|
|
214
|
+
grades: {
|
|
215
|
+
geo: geo.score,
|
|
216
|
+
coreWebVitals: coreWebVitals.overallScore,
|
|
217
|
+
security: security.grade,
|
|
218
|
+
structuredData: structuredData.score,
|
|
219
|
+
images: images.score,
|
|
220
|
+
internalLinking: internalLinking.score,
|
|
221
|
+
mobile: mobile.score,
|
|
222
|
+
},
|
|
223
|
+
geo,
|
|
224
|
+
coreWebVitals,
|
|
225
|
+
security,
|
|
226
|
+
structuredData,
|
|
227
|
+
images,
|
|
228
|
+
internalLinking,
|
|
229
|
+
mobile,
|
|
230
|
+
allIssues,
|
|
231
|
+
prioritizedRecommendations,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { analyzeInternalLinking, suggestInternalLinks } from './internal-linking-analyzer.js';
|
|
3
|
+
|
|
4
|
+
describe('Internal Linking Analyzer', () => {
|
|
5
|
+
describe('analyzeInternalLinking', () => {
|
|
6
|
+
it('counts internal and external links', () => {
|
|
7
|
+
const html = `
|
|
8
|
+
<html><body>
|
|
9
|
+
<a href="/about">About</a>
|
|
10
|
+
<a href="/products">Products</a>
|
|
11
|
+
<a href="https://external.com">External</a>
|
|
12
|
+
</body></html>
|
|
13
|
+
`;
|
|
14
|
+
const result = analyzeInternalLinking(html, 'https://example.com/page');
|
|
15
|
+
expect(result.internalLinks).toBe(2);
|
|
16
|
+
expect(result.externalLinks).toBe(1);
|
|
17
|
+
expect(result.totalLinks).toBe(3);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('distinguishes navigation from content links', () => {
|
|
21
|
+
const html = `
|
|
22
|
+
<html><body>
|
|
23
|
+
<nav>
|
|
24
|
+
<a href="/home">Home</a>
|
|
25
|
+
<a href="/about">About</a>
|
|
26
|
+
</nav>
|
|
27
|
+
<main>
|
|
28
|
+
<a href="/products">See our products</a>
|
|
29
|
+
</main>
|
|
30
|
+
</body></html>
|
|
31
|
+
`;
|
|
32
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
33
|
+
expect(result.navigationLinks).toBe(2);
|
|
34
|
+
expect(result.contentLinks).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('detects generic anchor text', () => {
|
|
38
|
+
const html = `
|
|
39
|
+
<html><body>
|
|
40
|
+
<a href="/about">Click here</a>
|
|
41
|
+
<a href="/products">Read more</a>
|
|
42
|
+
<a href="/services">Learn more</a>
|
|
43
|
+
<a href="/contact">here</a>
|
|
44
|
+
</body></html>
|
|
45
|
+
`;
|
|
46
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
47
|
+
expect(result.linksWithGenericText).toBe(4);
|
|
48
|
+
expect(result.issues.some(i => i.code === 'LINK_GENERIC_ANCHOR')).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('detects descriptive anchor text', () => {
|
|
52
|
+
const html = `
|
|
53
|
+
<html><body>
|
|
54
|
+
<a href="/about">About Our Company</a>
|
|
55
|
+
<a href="/products">Our Product Catalog</a>
|
|
56
|
+
</body></html>
|
|
57
|
+
`;
|
|
58
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
59
|
+
expect(result.linksWithKeywords).toBe(2);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('detects empty anchor text', () => {
|
|
63
|
+
const html = `
|
|
64
|
+
<html><body>
|
|
65
|
+
<a href="/about"></a>
|
|
66
|
+
<a href="/products"> </a>
|
|
67
|
+
</body></html>
|
|
68
|
+
`;
|
|
69
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
70
|
+
expect(result.anchorTextAnalysis.empty).toBe(2);
|
|
71
|
+
expect(result.issues.some(i => i.code === 'LINK_EMPTY_ANCHOR')).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('detects nofollow on internal links', () => {
|
|
75
|
+
const html = `
|
|
76
|
+
<html><body>
|
|
77
|
+
<a href="/about" rel="nofollow">About</a>
|
|
78
|
+
</body></html>
|
|
79
|
+
`;
|
|
80
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
81
|
+
expect(result.issues.some(i => i.code === 'LINK_NOFOLLOW_INTERNAL')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('tracks unique internal targets', () => {
|
|
85
|
+
const html = `
|
|
86
|
+
<html><body>
|
|
87
|
+
<a href="/about">About</a>
|
|
88
|
+
<a href="/about">About Us</a>
|
|
89
|
+
<a href="/products">Products</a>
|
|
90
|
+
</body></html>
|
|
91
|
+
`;
|
|
92
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
93
|
+
expect(result.uniqueInternalTargets).toBe(2);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('detects orphan risk', () => {
|
|
97
|
+
const html = `
|
|
98
|
+
<html><body>
|
|
99
|
+
<nav><a href="/home">Home</a></nav>
|
|
100
|
+
<p>Content without links</p>
|
|
101
|
+
</body></html>
|
|
102
|
+
`;
|
|
103
|
+
const result = analyzeInternalLinking(html, 'https://example.com/orphan');
|
|
104
|
+
expect(result.orphanRisk).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles image links', () => {
|
|
108
|
+
const html = `
|
|
109
|
+
<html><body>
|
|
110
|
+
<a href="/products"><img src="/logo.png" alt="Products"></a>
|
|
111
|
+
<a href="/about"><img src="/icon.png"></a>
|
|
112
|
+
</body></html>
|
|
113
|
+
`;
|
|
114
|
+
const result = analyzeInternalLinking(html, 'https://example.com');
|
|
115
|
+
expect(result.anchorTextAnalysis.imageLinks).toBe(2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('suggestInternalLinks', () => {
|
|
120
|
+
it('suggests links based on keyword matches', () => {
|
|
121
|
+
const content = '<p>Learn about SEO optimization techniques.</p>';
|
|
122
|
+
const pages = [
|
|
123
|
+
{ url: '/seo-guide', title: 'SEO Guide', keywords: ['seo', 'optimization'] },
|
|
124
|
+
{ url: '/about', title: 'About', keywords: ['company', 'team'] },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const suggestions = suggestInternalLinks(content, pages);
|
|
128
|
+
expect(suggestions.some(s => s.suggestedUrl === '/seo-guide')).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('does not suggest already linked pages', () => {
|
|
132
|
+
const content = '<p>Learn about SEO optimization. <a href="/seo-guide">Guide</a></p>';
|
|
133
|
+
const pages = [
|
|
134
|
+
{ url: '/seo-guide', title: 'SEO Guide', keywords: ['seo'] },
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const suggestions = suggestInternalLinks(content, pages);
|
|
138
|
+
expect(suggestions.length).toBe(0);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|