@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.
- package/dist/index.d.mts +77 -393
- package/dist/index.d.ts +77 -393
- package/dist/index.js +417 -6443
- package/dist/index.mjs +296 -6313
- package/package.json +6 -1
- package/scripts/build-deno.ts +0 -134
- package/src/analyzers/core-web-vitals-analyzer.test.ts +0 -236
- package/src/analyzers/core-web-vitals-analyzer.ts +0 -557
- package/src/analyzers/geo-analyzer.test.ts +0 -310
- package/src/analyzers/geo-analyzer.ts +0 -814
- package/src/analyzers/image-optimization-analyzer.test.ts +0 -145
- package/src/analyzers/image-optimization-analyzer.ts +0 -348
- package/src/analyzers/index.ts +0 -233
- package/src/analyzers/internal-linking-analyzer.test.ts +0 -141
- package/src/analyzers/internal-linking-analyzer.ts +0 -419
- package/src/analyzers/mobile-seo-analyzer.test.ts +0 -140
- package/src/analyzers/mobile-seo-analyzer.ts +0 -455
- package/src/analyzers/security-headers-analyzer.test.ts +0 -115
- package/src/analyzers/security-headers-analyzer.ts +0 -318
- package/src/analyzers/structured-data-analyzer.test.ts +0 -210
- package/src/analyzers/structured-data-analyzer.ts +0 -590
- package/src/audit/ai/analyzer.ts +0 -347
- package/src/audit/ai/index.ts +0 -29
- package/src/audit/ai/prompts/content-analysis.ts +0 -271
- package/src/audit/ai/types.ts +0 -179
- package/src/audit/checks/additional-checks.ts +0 -439
- package/src/audit/checks/ai-citation-worthiness.ts +0 -399
- package/src/audit/checks/ai-content-structure.ts +0 -325
- package/src/audit/checks/ai-readiness.ts +0 -339
- package/src/audit/checks/anchor-text.ts +0 -179
- package/src/audit/checks/answer-conciseness.ts +0 -322
- package/src/audit/checks/asset-minification.ts +0 -270
- package/src/audit/checks/bing-optimization.ts +0 -206
- package/src/audit/checks/brand-mention-optimization.ts +0 -349
- package/src/audit/checks/caching-headers.ts +0 -305
- package/src/audit/checks/canonical-advanced.ts +0 -150
- package/src/audit/checks/canonical-domain.ts +0 -196
- package/src/audit/checks/citation-quality.ts +0 -358
- package/src/audit/checks/client-rendering.ts +0 -542
- package/src/audit/checks/color-contrast.ts +0 -342
- package/src/audit/checks/content-freshness.ts +0 -170
- package/src/audit/checks/content-science.ts +0 -589
- package/src/audit/checks/conversion-elements.ts +0 -526
- package/src/audit/checks/crawlability.ts +0 -220
- package/src/audit/checks/directory-listing.ts +0 -172
- package/src/audit/checks/dom-analysis.ts +0 -191
- package/src/audit/checks/dom-size.ts +0 -246
- package/src/audit/checks/duplicate-content.ts +0 -194
- package/src/audit/checks/eeat-signals.ts +0 -990
- package/src/audit/checks/entity-seo.ts +0 -396
- package/src/audit/checks/featured-snippet.ts +0 -473
- package/src/audit/checks/freshness-signals.ts +0 -443
- package/src/audit/checks/funnel-intent.ts +0 -463
- package/src/audit/checks/hreflang.ts +0 -174
- package/src/audit/checks/html-compliance.ts +0 -302
- package/src/audit/checks/image-dimensions.ts +0 -167
- package/src/audit/checks/images.ts +0 -160
- package/src/audit/checks/indexnow.ts +0 -275
- package/src/audit/checks/interactive-tools.ts +0 -475
- package/src/audit/checks/internal-link-graph.ts +0 -436
- package/src/audit/checks/keyword-analysis.ts +0 -239
- package/src/audit/checks/keyword-cannibalization.ts +0 -385
- package/src/audit/checks/keyword-placement.ts +0 -471
- package/src/audit/checks/links.ts +0 -273
- package/src/audit/checks/llms-txt.ts +0 -224
- package/src/audit/checks/local-seo.ts +0 -296
- package/src/audit/checks/mobile.ts +0 -167
- package/src/audit/checks/modern-images.ts +0 -226
- package/src/audit/checks/navboost-signals.ts +0 -395
- package/src/audit/checks/on-page.ts +0 -209
- package/src/audit/checks/page-resources.ts +0 -285
- package/src/audit/checks/pagination.ts +0 -180
- package/src/audit/checks/performance.ts +0 -153
- package/src/audit/checks/platform-presence.ts +0 -580
- package/src/audit/checks/redirect-analysis.ts +0 -153
- package/src/audit/checks/redirect-chain.ts +0 -389
- package/src/audit/checks/resource-hints.ts +0 -420
- package/src/audit/checks/responsive-css.ts +0 -247
- package/src/audit/checks/responsive-images.ts +0 -396
- package/src/audit/checks/review-ecosystem.ts +0 -415
- package/src/audit/checks/robots-validation.ts +0 -373
- package/src/audit/checks/security-headers.ts +0 -188
- package/src/audit/checks/security.ts +0 -144
- package/src/audit/checks/serp-preview.ts +0 -251
- package/src/audit/checks/site-maturity.ts +0 -453
- package/src/audit/checks/social-meta.test.ts +0 -275
- package/src/audit/checks/social-meta.ts +0 -134
- package/src/audit/checks/soft-404.ts +0 -151
- package/src/audit/checks/structured-data.ts +0 -238
- package/src/audit/checks/tech-detection.ts +0 -496
- package/src/audit/checks/topical-clusters.ts +0 -435
- package/src/audit/checks/tracker-bloat.ts +0 -462
- package/src/audit/checks/tracking-verification.test.ts +0 -371
- package/src/audit/checks/tracking-verification.ts +0 -636
- package/src/audit/checks/url-safety.ts +0 -682
- package/src/audit/deno-entry.ts +0 -66
- package/src/audit/discovery/index.ts +0 -15
- package/src/audit/discovery/link-crawler.ts +0 -233
- package/src/audit/discovery/repo-routes.ts +0 -347
- package/src/audit/engine.ts +0 -627
- package/src/audit/fixes/index.ts +0 -209
- package/src/audit/fixes/social-meta-fixes.test.ts +0 -329
- package/src/audit/fixes/social-meta-fixes.ts +0 -463
- package/src/audit/index.ts +0 -74
- package/src/audit/runner.test.ts +0 -299
- package/src/audit/runner.ts +0 -130
- package/src/audit/types.ts +0 -1963
- package/src/content/featured-snippet.ts +0 -367
- package/src/content/generator.test.ts +0 -534
- package/src/content/generator.ts +0 -501
- package/src/content/headline.ts +0 -317
- package/src/content/index.ts +0 -62
- package/src/content/intent.ts +0 -258
- package/src/content/keyword-density.ts +0 -349
- package/src/content/readability.ts +0 -262
- package/src/executor.ts +0 -336
- package/src/fixer/framework-fixes.test.ts +0 -489
- package/src/fixer/framework-fixes.ts +0 -5287
- package/src/fixer/index.ts +0 -6
- package/src/fixer/schemas.ts +0 -971
- package/src/fixer.ts +0 -642
- package/src/frameworks/detector.test.ts +0 -248
- package/src/frameworks/detector.ts +0 -899
- package/src/frameworks/index.ts +0 -68
- package/src/frameworks/recipes/angular.yaml +0 -171
- package/src/frameworks/recipes/astro.yaml +0 -206
- package/src/frameworks/recipes/django.yaml +0 -180
- package/src/frameworks/recipes/laravel.yaml +0 -137
- package/src/frameworks/recipes/nextjs.yaml +0 -268
- package/src/frameworks/recipes/nuxt.yaml +0 -175
- package/src/frameworks/recipes/rails.yaml +0 -188
- package/src/frameworks/recipes/react.yaml +0 -202
- package/src/frameworks/recipes/sveltekit.yaml +0 -154
- package/src/frameworks/recipes/vue.yaml +0 -137
- package/src/frameworks/recipes/wordpress.yaml +0 -209
- package/src/frameworks/suggestion-engine.ts +0 -357
- package/src/geo/geo-content.test.ts +0 -305
- package/src/geo/geo-content.ts +0 -266
- package/src/geo/geo-history.test.ts +0 -473
- package/src/geo/geo-history.ts +0 -433
- package/src/geo/geo-tracker.test.ts +0 -359
- package/src/geo/geo-tracker.ts +0 -411
- package/src/geo/index.ts +0 -11
- package/src/geo/llm-citation-checker.ts +0 -188
- package/src/git/commit-helper.test.ts +0 -261
- package/src/git/commit-helper.ts +0 -329
- package/src/git/index.ts +0 -12
- package/src/git/pr-helper.test.ts +0 -284
- package/src/git/pr-helper.ts +0 -307
- package/src/index.ts +0 -76
- package/src/keywords/ai-keyword-engine.ts +0 -1062
- package/src/keywords/ai-summarizer.ts +0 -387
- package/src/keywords/ci-mode.ts +0 -555
- package/src/keywords/engine.ts +0 -359
- package/src/keywords/index.ts +0 -151
- package/src/keywords/llm-judge.ts +0 -357
- package/src/keywords/nlp-analysis.ts +0 -706
- package/src/keywords/prioritizer.ts +0 -295
- package/src/keywords/site-crawler.ts +0 -342
- package/src/keywords/sources/autocomplete.ts +0 -139
- package/src/keywords/sources/competitive-search.ts +0 -450
- package/src/keywords/sources/competitor-analysis.ts +0 -374
- package/src/keywords/sources/dataforseo.ts +0 -206
- package/src/keywords/sources/free-sources.ts +0 -294
- package/src/keywords/sources/gsc.ts +0 -123
- package/src/keywords/topic-grouping.ts +0 -327
- package/src/keywords/types.ts +0 -144
- package/src/keywords/wizard.ts +0 -457
- package/src/loader.ts +0 -40
- package/src/ranking/index.ts +0 -5
- package/src/ranking/serp-client.ts +0 -348
- package/src/ranking/tracker.ts +0 -380
- package/src/ranking/types.ts +0 -123
- package/src/reports/index.ts +0 -7
- package/src/reports/report-generator.test.ts +0 -293
- package/src/reports/report-generator.ts +0 -713
- package/src/scheduler/alerts.test.ts +0 -458
- package/src/scheduler/alerts.ts +0 -328
- package/src/scheduler/index.ts +0 -8
- package/src/scheduler/scheduled-audit.test.ts +0 -377
- package/src/scheduler/scheduled-audit.ts +0 -149
- package/src/test/integration-test.ts +0 -325
- package/src/tools/analyzer.ts +0 -373
- package/src/tools/crawl.ts +0 -293
- package/src/tools/files.ts +0 -301
- package/src/tools/h1-fixer.ts +0 -249
- package/src/tools/index.ts +0 -67
- package/src/tracking/github-action.ts +0 -326
- package/src/tracking/google-analytics.ts +0 -265
- package/src/tracking/index.ts +0 -45
- package/src/tracking/report-generator.ts +0 -386
- package/src/tracking/search-console.ts +0 -335
- package/src/types.ts +0 -148
- package/src/utils/http.ts +0 -302
- package/src/wasm-adapter.ts +0 -297
- package/src/wasm-entry.ts +0 -14
- package/tsconfig.json +0 -17
- package/tsup.wasm.config.ts +0 -26
- package/vitest.config.ts +0 -15
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
generateAICitableContent,
|
|
4
|
-
generateFAQSchema,
|
|
5
|
-
generateComparisonTable,
|
|
6
|
-
generateKeyFacts,
|
|
7
|
-
optimizeForAI,
|
|
8
|
-
AIContentConfig,
|
|
9
|
-
FAQItem,
|
|
10
|
-
ComparisonRow,
|
|
11
|
-
KeyFact,
|
|
12
|
-
} from './geo-content.js';
|
|
13
|
-
|
|
14
|
-
describe('geo-content', () => {
|
|
15
|
-
describe('generateKeyFacts', () => {
|
|
16
|
-
it('formats facts with bold markers', () => {
|
|
17
|
-
const facts: KeyFact[] = [
|
|
18
|
-
{ label: 'Users', value: '10,000+' },
|
|
19
|
-
{ label: 'Checks', value: '200+' },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const content = generateKeyFacts(facts);
|
|
23
|
-
|
|
24
|
-
expect(content).toContain('**Users:** 10,000+');
|
|
25
|
-
expect(content).toContain('**Checks:** 200+');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('generates bullet list format', () => {
|
|
29
|
-
const facts: KeyFact[] = [
|
|
30
|
-
{ label: 'Speed', value: 'Fast' },
|
|
31
|
-
{ label: 'Price', value: 'Free' },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const content = generateKeyFacts(facts, { format: 'bullets' });
|
|
35
|
-
|
|
36
|
-
expect(content).toContain('- **Speed:** Fast');
|
|
37
|
-
expect(content).toContain('- **Price:** Free');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('generates paragraph format', () => {
|
|
41
|
-
const facts: KeyFact[] = [
|
|
42
|
-
{ label: 'Founded', value: '2024' },
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const content = generateKeyFacts(facts, { format: 'paragraph' });
|
|
46
|
-
|
|
47
|
-
expect(content).not.toContain('- ');
|
|
48
|
-
expect(content).toContain('**Founded:** 2024');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('generateFAQSchema', () => {
|
|
53
|
-
it('generates valid FAQ schema JSON-LD', () => {
|
|
54
|
-
const faqs: FAQItem[] = [
|
|
55
|
-
{ question: 'What is SEO Autopilot?', answer: 'A CLI-first SEO tool.' },
|
|
56
|
-
{ question: 'How much does it cost?', answer: 'Free tier available.' },
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
const schema = generateFAQSchema(faqs);
|
|
60
|
-
const parsed = JSON.parse(schema);
|
|
61
|
-
|
|
62
|
-
expect(parsed['@context']).toBe('https://schema.org');
|
|
63
|
-
expect(parsed['@type']).toBe('FAQPage');
|
|
64
|
-
expect(parsed.mainEntity).toHaveLength(2);
|
|
65
|
-
expect(parsed.mainEntity[0]['@type']).toBe('Question');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('escapes special characters in JSON', () => {
|
|
69
|
-
const faqs: FAQItem[] = [
|
|
70
|
-
{ question: 'What about "quotes"?', answer: 'They work fine.' },
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const schema = generateFAQSchema(faqs);
|
|
74
|
-
|
|
75
|
-
// Should be valid JSON
|
|
76
|
-
expect(() => JSON.parse(schema)).not.toThrow();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('generates markdown FAQ section', () => {
|
|
80
|
-
const faqs: FAQItem[] = [
|
|
81
|
-
{ question: 'How do I install?', answer: 'Run npm install.' },
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
const markdown = generateFAQSchema(faqs, { format: 'markdown' });
|
|
85
|
-
|
|
86
|
-
expect(markdown).toContain('## FAQ');
|
|
87
|
-
expect(markdown).toContain('### How do I install?');
|
|
88
|
-
expect(markdown).toContain('Run npm install.');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('generateComparisonTable', () => {
|
|
93
|
-
it('generates markdown table', () => {
|
|
94
|
-
const rows: ComparisonRow[] = [
|
|
95
|
-
{ feature: 'CLI Tool', brand: 'Yes', competitor: 'No' },
|
|
96
|
-
{ feature: 'Auto-fix PRs', brand: 'Yes', competitor: 'No' },
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const table = generateComparisonTable(rows, {
|
|
100
|
-
brandName: 'SEO Autopilot',
|
|
101
|
-
competitorName: 'Ahrefs',
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
expect(table).toContain('| Feature | SEO Autopilot | Ahrefs |');
|
|
105
|
-
expect(table).toContain('| CLI Tool | Yes | No |');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('uses checkmarks when specified', () => {
|
|
109
|
-
const rows: ComparisonRow[] = [
|
|
110
|
-
{ feature: 'Free Tier', brand: true, competitor: false },
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
const table = generateComparisonTable(rows, {
|
|
114
|
-
brandName: 'Us',
|
|
115
|
-
competitorName: 'Them',
|
|
116
|
-
useCheckmarks: true,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
expect(table).toMatch(/✓|✅/);
|
|
120
|
-
expect(table).toMatch(/✗|❌|-/);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('handles multiple competitors', () => {
|
|
124
|
-
const rows: ComparisonRow[] = [
|
|
125
|
-
{ feature: 'Price', brand: '$29', competitors: { 'Ahrefs': '$99', 'SEMrush': '$129' } },
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
const table = generateComparisonTable(rows, {
|
|
129
|
-
brandName: 'SEO Autopilot',
|
|
130
|
-
competitorNames: ['Ahrefs', 'SEMrush'],
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(table).toContain('Ahrefs');
|
|
134
|
-
expect(table).toContain('SEMrush');
|
|
135
|
-
expect(table).toContain('$99');
|
|
136
|
-
expect(table).toContain('$129');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('generateAICitableContent', () => {
|
|
141
|
-
it('generates structured content block', () => {
|
|
142
|
-
const config: AIContentConfig = {
|
|
143
|
-
productName: 'SEO Autopilot',
|
|
144
|
-
tagline: 'CLI-first SEO automation',
|
|
145
|
-
keyFacts: [
|
|
146
|
-
{ label: 'Checks', value: '200+' },
|
|
147
|
-
],
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const content = generateAICitableContent(config);
|
|
151
|
-
|
|
152
|
-
expect(content).toContain('SEO Autopilot');
|
|
153
|
-
expect(content).toContain('CLI-first SEO automation');
|
|
154
|
-
expect(content).toContain('**Checks:** 200+');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('includes FAQ section when provided', () => {
|
|
158
|
-
const config: AIContentConfig = {
|
|
159
|
-
productName: 'Test Product',
|
|
160
|
-
faqs: [
|
|
161
|
-
{ question: 'What is it?', answer: 'A great product.' },
|
|
162
|
-
],
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const content = generateAICitableContent(config);
|
|
166
|
-
|
|
167
|
-
expect(content).toContain('What is it?');
|
|
168
|
-
expect(content).toContain('A great product.');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('includes comparison table when provided', () => {
|
|
172
|
-
const config: AIContentConfig = {
|
|
173
|
-
productName: 'SEO Autopilot',
|
|
174
|
-
comparison: {
|
|
175
|
-
competitorName: 'Competitor',
|
|
176
|
-
rows: [
|
|
177
|
-
{ feature: 'CLI', brand: 'Yes', competitor: 'No' },
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const content = generateAICitableContent(config);
|
|
183
|
-
|
|
184
|
-
expect(content).toContain('CLI');
|
|
185
|
-
expect(content).toContain('Yes');
|
|
186
|
-
expect(content).toContain('No');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('generates all sections in correct order', () => {
|
|
190
|
-
const config: AIContentConfig = {
|
|
191
|
-
productName: 'SEO Autopilot',
|
|
192
|
-
tagline: 'The developer SEO tool',
|
|
193
|
-
description: 'Full description here.',
|
|
194
|
-
keyFacts: [{ label: 'Users', value: '5000+' }],
|
|
195
|
-
faqs: [{ question: 'Q?', answer: 'A.' }],
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const content = generateAICitableContent(config);
|
|
199
|
-
|
|
200
|
-
const taglineIndex = content.indexOf('developer SEO tool');
|
|
201
|
-
const factsIndex = content.indexOf('Users');
|
|
202
|
-
const faqIndex = content.indexOf('Q?');
|
|
203
|
-
|
|
204
|
-
expect(taglineIndex).toBeLessThan(factsIndex);
|
|
205
|
-
expect(factsIndex).toBeLessThan(faqIndex);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe('optimizeForAI', () => {
|
|
210
|
-
it('adds citable markers to key statements', () => {
|
|
211
|
-
const content = 'SEO Autopilot runs 200 checks on your website.';
|
|
212
|
-
|
|
213
|
-
const optimized = optimizeForAI(content, {
|
|
214
|
-
highlightNumbers: true,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
expect(optimized).toContain('**200**');
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('converts lists to structured format', () => {
|
|
221
|
-
const content = `Features include:
|
|
222
|
-
speed, accuracy, and simplicity.`;
|
|
223
|
-
|
|
224
|
-
const optimized = optimizeForAI(content, {
|
|
225
|
-
structureLists: true,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
expect(optimized).toContain('- ');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('adds emphasis to brand mentions', () => {
|
|
232
|
-
const content = 'Use SEO Autopilot for better rankings.';
|
|
233
|
-
|
|
234
|
-
const optimized = optimizeForAI(content, {
|
|
235
|
-
brandName: 'SEO Autopilot',
|
|
236
|
-
emphasizeBrand: true,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
expect(optimized).toContain('**SEO Autopilot**');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('preserves existing markdown', () => {
|
|
243
|
-
const content = 'This is **already bold** text.';
|
|
244
|
-
|
|
245
|
-
const optimized = optimizeForAI(content, {
|
|
246
|
-
highlightNumbers: true,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
expect(optimized).toContain('**already bold**');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('adds source attribution placeholder', () => {
|
|
253
|
-
const content = 'Some facts here.';
|
|
254
|
-
|
|
255
|
-
const optimized = optimizeForAI(content, {
|
|
256
|
-
addSourcePlaceholder: true,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
expect(optimized).toContain('Source:');
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
describe('integration', () => {
|
|
264
|
-
it('generates complete AI-optimized landing content', () => {
|
|
265
|
-
const config: AIContentConfig = {
|
|
266
|
-
productName: 'SEO Autopilot',
|
|
267
|
-
tagline: 'The CLI-First SEO Tool for Developers',
|
|
268
|
-
description: 'SEO Autopilot runs 200+ automated SEO checks directly from your terminal, making it the only developer-native SEO tool on the market.',
|
|
269
|
-
keyFacts: [
|
|
270
|
-
{ label: 'Automated Checks', value: '200+' },
|
|
271
|
-
{ label: 'GitHub Stars', value: '5,000+' },
|
|
272
|
-
{ label: 'Monthly Downloads', value: '50,000+' },
|
|
273
|
-
],
|
|
274
|
-
faqs: [
|
|
275
|
-
{
|
|
276
|
-
question: 'What is SEO Autopilot?',
|
|
277
|
-
answer: 'SEO Autopilot is a CLI-first SEO automation tool designed specifically for developers. It runs 200+ checks and can auto-fix issues via pull requests.',
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
question: 'How much does SEO Autopilot cost?',
|
|
281
|
-
answer: 'SEO Autopilot offers a free tier with 50 checks. Paid plans start at $9/month for Solo developers and $29/month for Pro teams.',
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
comparison: {
|
|
285
|
-
competitorName: 'Ahrefs',
|
|
286
|
-
rows: [
|
|
287
|
-
{ feature: 'CLI Tool', brand: 'Yes', competitor: 'No' },
|
|
288
|
-
{ feature: 'Auto-fix PRs', brand: 'Yes', competitor: 'No' },
|
|
289
|
-
{ feature: 'Free Tier', brand: 'Yes', competitor: 'No' },
|
|
290
|
-
{ feature: 'Starting Price', brand: '$9/mo', competitor: '$99/mo' },
|
|
291
|
-
],
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const content = generateAICitableContent(config);
|
|
296
|
-
|
|
297
|
-
// Should have all major sections
|
|
298
|
-
expect(content).toContain('SEO Autopilot');
|
|
299
|
-
expect(content).toContain('200+');
|
|
300
|
-
expect(content).toContain('What is SEO Autopilot?');
|
|
301
|
-
expect(content).toContain('CLI Tool');
|
|
302
|
-
expect(content).toContain('$9/mo');
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
});
|
package/src/geo/geo-content.ts
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GEO Content Module
|
|
3
|
-
*
|
|
4
|
-
* Generate AI-citation-friendly content with structured data,
|
|
5
|
-
* comparison tables, FAQs, and key facts formatted for LLM parsing.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface KeyFact {
|
|
9
|
-
label: string;
|
|
10
|
-
value: string | number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FAQItem {
|
|
14
|
-
question: string;
|
|
15
|
-
answer: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ComparisonRow {
|
|
19
|
-
feature: string;
|
|
20
|
-
brand: string | boolean;
|
|
21
|
-
competitor?: string | boolean;
|
|
22
|
-
competitors?: Record<string, string | boolean>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ComparisonConfig {
|
|
26
|
-
brandName: string;
|
|
27
|
-
competitorName?: string;
|
|
28
|
-
competitorNames?: string[];
|
|
29
|
-
useCheckmarks?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AIContentConfig {
|
|
33
|
-
productName: string;
|
|
34
|
-
tagline?: string;
|
|
35
|
-
description?: string;
|
|
36
|
-
keyFacts?: KeyFact[];
|
|
37
|
-
faqs?: FAQItem[];
|
|
38
|
-
comparison?: {
|
|
39
|
-
competitorName: string;
|
|
40
|
-
rows: ComparisonRow[];
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface KeyFactsOptions {
|
|
45
|
-
format?: 'bullets' | 'paragraph' | 'inline';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface FAQSchemaOptions {
|
|
49
|
-
format?: 'json-ld' | 'markdown';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface OptimizeOptions {
|
|
53
|
-
brandName?: string;
|
|
54
|
-
highlightNumbers?: boolean;
|
|
55
|
-
structureLists?: boolean;
|
|
56
|
-
emphasizeBrand?: boolean;
|
|
57
|
-
addSourcePlaceholder?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Generate formatted key facts for AI citation
|
|
62
|
-
*/
|
|
63
|
-
export function generateKeyFacts(
|
|
64
|
-
facts: KeyFact[],
|
|
65
|
-
options: KeyFactsOptions = {}
|
|
66
|
-
): string {
|
|
67
|
-
const format = options.format || 'bullets';
|
|
68
|
-
|
|
69
|
-
const formatted = facts.map(fact => `**${fact.label}:** ${fact.value}`);
|
|
70
|
-
|
|
71
|
-
switch (format) {
|
|
72
|
-
case 'bullets':
|
|
73
|
-
return formatted.map(f => `- ${f}`).join('\n');
|
|
74
|
-
case 'paragraph':
|
|
75
|
-
return formatted.join('. ') + '.';
|
|
76
|
-
case 'inline':
|
|
77
|
-
return formatted.join(' | ');
|
|
78
|
-
default:
|
|
79
|
-
return formatted.join('\n');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Generate FAQ schema (JSON-LD or Markdown)
|
|
85
|
-
*/
|
|
86
|
-
export function generateFAQSchema(
|
|
87
|
-
faqs: FAQItem[],
|
|
88
|
-
options: FAQSchemaOptions = {}
|
|
89
|
-
): string {
|
|
90
|
-
const format = options.format || 'json-ld';
|
|
91
|
-
|
|
92
|
-
if (format === 'markdown') {
|
|
93
|
-
const lines = ['## FAQ', ''];
|
|
94
|
-
for (const faq of faqs) {
|
|
95
|
-
lines.push(`### ${faq.question}`);
|
|
96
|
-
lines.push('');
|
|
97
|
-
lines.push(faq.answer);
|
|
98
|
-
lines.push('');
|
|
99
|
-
}
|
|
100
|
-
return lines.join('\n').trim();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// JSON-LD format
|
|
104
|
-
const schema = {
|
|
105
|
-
'@context': 'https://schema.org',
|
|
106
|
-
'@type': 'FAQPage',
|
|
107
|
-
mainEntity: faqs.map(faq => ({
|
|
108
|
-
'@type': 'Question',
|
|
109
|
-
name: faq.question,
|
|
110
|
-
acceptedAnswer: {
|
|
111
|
-
'@type': 'Answer',
|
|
112
|
-
text: faq.answer,
|
|
113
|
-
},
|
|
114
|
-
})),
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return JSON.stringify(schema, null, 2);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Generate comparison table in markdown
|
|
122
|
-
*/
|
|
123
|
-
export function generateComparisonTable(
|
|
124
|
-
rows: ComparisonRow[],
|
|
125
|
-
config: ComparisonConfig
|
|
126
|
-
): string {
|
|
127
|
-
const { brandName, useCheckmarks } = config;
|
|
128
|
-
|
|
129
|
-
// Determine competitor columns
|
|
130
|
-
const competitorNames = config.competitorNames ||
|
|
131
|
-
(config.competitorName ? [config.competitorName] : []);
|
|
132
|
-
|
|
133
|
-
// Build header
|
|
134
|
-
const headers = ['Feature', brandName, ...competitorNames];
|
|
135
|
-
const headerRow = `| ${headers.join(' | ')} |`;
|
|
136
|
-
const separatorRow = `| ${headers.map(() => '---').join(' | ')} |`;
|
|
137
|
-
|
|
138
|
-
// Build rows
|
|
139
|
-
const dataRows = rows.map(row => {
|
|
140
|
-
const brandValue = formatCellValue(row.brand, useCheckmarks);
|
|
141
|
-
|
|
142
|
-
let competitorValues: string[];
|
|
143
|
-
if (row.competitors) {
|
|
144
|
-
competitorValues = competitorNames.map(name =>
|
|
145
|
-
formatCellValue(row.competitors?.[name] ?? '-', useCheckmarks)
|
|
146
|
-
);
|
|
147
|
-
} else {
|
|
148
|
-
competitorValues = [formatCellValue(row.competitor ?? '-', useCheckmarks)];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return `| ${row.feature} | ${brandValue} | ${competitorValues.join(' | ')} |`;
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return [headerRow, separatorRow, ...dataRows].join('\n');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Format cell value for comparison table
|
|
159
|
-
*/
|
|
160
|
-
function formatCellValue(value: string | boolean | undefined, useCheckmarks?: boolean): string {
|
|
161
|
-
if (typeof value === 'boolean') {
|
|
162
|
-
if (useCheckmarks) {
|
|
163
|
-
return value ? '✓' : '✗';
|
|
164
|
-
}
|
|
165
|
-
return value ? 'Yes' : 'No';
|
|
166
|
-
}
|
|
167
|
-
return String(value ?? '-');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Generate complete AI-citable content block
|
|
172
|
-
*/
|
|
173
|
-
export function generateAICitableContent(config: AIContentConfig): string {
|
|
174
|
-
const sections: string[] = [];
|
|
175
|
-
|
|
176
|
-
// Header with product name
|
|
177
|
-
sections.push(`# ${config.productName}`);
|
|
178
|
-
sections.push('');
|
|
179
|
-
|
|
180
|
-
// Tagline
|
|
181
|
-
if (config.tagline) {
|
|
182
|
-
sections.push(`**${config.tagline}**`);
|
|
183
|
-
sections.push('');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Description
|
|
187
|
-
if (config.description) {
|
|
188
|
-
sections.push(config.description);
|
|
189
|
-
sections.push('');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Key Facts
|
|
193
|
-
if (config.keyFacts && config.keyFacts.length > 0) {
|
|
194
|
-
sections.push('## Key Facts');
|
|
195
|
-
sections.push('');
|
|
196
|
-
sections.push(generateKeyFacts(config.keyFacts, { format: 'bullets' }));
|
|
197
|
-
sections.push('');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Comparison Table
|
|
201
|
-
if (config.comparison) {
|
|
202
|
-
sections.push(`## ${config.productName} vs ${config.comparison.competitorName}`);
|
|
203
|
-
sections.push('');
|
|
204
|
-
sections.push(generateComparisonTable(config.comparison.rows, {
|
|
205
|
-
brandName: config.productName,
|
|
206
|
-
competitorName: config.comparison.competitorName,
|
|
207
|
-
}));
|
|
208
|
-
sections.push('');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// FAQ Section
|
|
212
|
-
if (config.faqs && config.faqs.length > 0) {
|
|
213
|
-
sections.push(generateFAQSchema(config.faqs, { format: 'markdown' }));
|
|
214
|
-
sections.push('');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return sections.join('\n').trim();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Optimize existing content for AI citation
|
|
222
|
-
*/
|
|
223
|
-
export function optimizeForAI(
|
|
224
|
-
content: string,
|
|
225
|
-
options: OptimizeOptions = {}
|
|
226
|
-
): string {
|
|
227
|
-
let optimized = content;
|
|
228
|
-
|
|
229
|
-
// Highlight numbers
|
|
230
|
-
if (options.highlightNumbers) {
|
|
231
|
-
// Match numbers not already in bold/code and not part of dates
|
|
232
|
-
optimized = optimized.replace(
|
|
233
|
-
/(?<!\*\*)(?<![`\d])\b(\d+(?:,\d{3})*(?:\.\d+)?)\b(?!\*\*)(?![`\d])/g,
|
|
234
|
-
'**$1**'
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Structure comma-separated lists
|
|
239
|
-
if (options.structureLists) {
|
|
240
|
-
// Match patterns like "includes: x, y, and z"
|
|
241
|
-
optimized = optimized.replace(
|
|
242
|
-
/:\s*([^.]+),\s*([^.]+),\s*and\s+([^.]+)\./gi,
|
|
243
|
-
':\n- $1\n- $2\n- $3'
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Emphasize brand name
|
|
248
|
-
if (options.brandName && options.emphasizeBrand) {
|
|
249
|
-
const brandRegex = new RegExp(`(?<!\\*\\*)\\b(${escapeRegex(options.brandName)})\\b(?!\\*\\*)`, 'g');
|
|
250
|
-
optimized = optimized.replace(brandRegex, '**$1**');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Add source placeholder
|
|
254
|
-
if (options.addSourcePlaceholder) {
|
|
255
|
-
optimized = optimized.trim() + '\n\n*Source: [Your Website URL]*';
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return optimized;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Escape special regex characters
|
|
263
|
-
*/
|
|
264
|
-
function escapeRegex(str: string): string {
|
|
265
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
266
|
-
}
|