@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,284 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
formatPRTitle,
|
|
4
|
-
formatPRBody,
|
|
5
|
-
generatePRDescription,
|
|
6
|
-
createPullRequest,
|
|
7
|
-
type PRConfig,
|
|
8
|
-
type SEOFixSummary,
|
|
9
|
-
type PRResult,
|
|
10
|
-
} from './pr-helper.js';
|
|
11
|
-
|
|
12
|
-
describe('pr-helper', () => {
|
|
13
|
-
describe('formatPRTitle', () => {
|
|
14
|
-
it('formats title for single category fix', () => {
|
|
15
|
-
const fixes: SEOFixSummary[] = [
|
|
16
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 2 },
|
|
17
|
-
];
|
|
18
|
-
expect(formatPRTitle(fixes)).toBe('fix(meta): SEO improvements - 1 issue fixed');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('formats title for multiple issues in same category', () => {
|
|
22
|
-
const fixes: SEOFixSummary[] = [
|
|
23
|
-
{ category: 'on-page', issues: ['TITLE_MISSING', 'META_DESC_MISSING'], filesCount: 2 },
|
|
24
|
-
];
|
|
25
|
-
expect(formatPRTitle(fixes)).toBe('fix(meta): SEO improvements - 2 issues fixed');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('formats title for multiple categories', () => {
|
|
29
|
-
const fixes: SEOFixSummary[] = [
|
|
30
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
31
|
-
{ category: 'social-meta', issues: ['OG_TITLE_MISSING', 'OG_IMAGE_MISSING'], filesCount: 1 },
|
|
32
|
-
];
|
|
33
|
-
expect(formatPRTitle(fixes)).toBe('fix(seo): SEO improvements - 3 issues fixed');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('handles empty fixes array', () => {
|
|
37
|
-
expect(formatPRTitle([])).toBe('fix(seo): SEO audit fixes');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('uses feat for new features like schema', () => {
|
|
41
|
-
const fixes: SEOFixSummary[] = [
|
|
42
|
-
{ category: 'structured-data', issues: ['SCHEMA_MISSING'], filesCount: 1 },
|
|
43
|
-
];
|
|
44
|
-
expect(formatPRTitle(fixes)).toBe('feat(schema): SEO improvements - 1 issue fixed');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('formatPRBody', () => {
|
|
49
|
-
it('generates body with summary section', () => {
|
|
50
|
-
const fixes: SEOFixSummary[] = [
|
|
51
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 2 },
|
|
52
|
-
];
|
|
53
|
-
const body = formatPRBody(fixes);
|
|
54
|
-
|
|
55
|
-
expect(body).toContain('## Summary');
|
|
56
|
-
expect(body).toContain('1 SEO issue');
|
|
57
|
-
expect(body).toContain('2 files');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('includes issue codes in body', () => {
|
|
61
|
-
const fixes: SEOFixSummary[] = [
|
|
62
|
-
{ category: 'on-page', issues: ['TITLE_MISSING', 'META_DESC_MISSING'], filesCount: 2 },
|
|
63
|
-
];
|
|
64
|
-
const body = formatPRBody(fixes);
|
|
65
|
-
|
|
66
|
-
expect(body).toContain('TITLE_MISSING');
|
|
67
|
-
expect(body).toContain('META_DESC_MISSING');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('includes category breakdown', () => {
|
|
71
|
-
const fixes: SEOFixSummary[] = [
|
|
72
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
73
|
-
{ category: 'social-meta', issues: ['OG_TITLE_MISSING'], filesCount: 1 },
|
|
74
|
-
];
|
|
75
|
-
const body = formatPRBody(fixes);
|
|
76
|
-
|
|
77
|
-
expect(body).toContain('On-Page');
|
|
78
|
-
expect(body).toContain('Social Meta');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('includes test plan section', () => {
|
|
82
|
-
const fixes: SEOFixSummary[] = [
|
|
83
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
84
|
-
];
|
|
85
|
-
const body = formatPRBody(fixes);
|
|
86
|
-
|
|
87
|
-
expect(body).toContain('## Test plan');
|
|
88
|
-
expect(body).toContain('- [ ]');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('includes generated by footer', () => {
|
|
92
|
-
const fixes: SEOFixSummary[] = [];
|
|
93
|
-
const body = formatPRBody(fixes);
|
|
94
|
-
|
|
95
|
-
expect(body).toContain('Generated with');
|
|
96
|
-
expect(body).toContain('SEO Autopilot');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('includes score improvement if provided', () => {
|
|
100
|
-
const fixes: SEOFixSummary[] = [
|
|
101
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
102
|
-
];
|
|
103
|
-
const body = formatPRBody(fixes, { scoreBefore: 65, scoreAfter: 82 });
|
|
104
|
-
|
|
105
|
-
expect(body).toContain('65');
|
|
106
|
-
expect(body).toContain('82');
|
|
107
|
-
expect(body).toContain('+17');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('generatePRDescription', () => {
|
|
112
|
-
it('generates full PR description with title and body', () => {
|
|
113
|
-
const fixes: SEOFixSummary[] = [
|
|
114
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
115
|
-
];
|
|
116
|
-
const description = generatePRDescription(fixes);
|
|
117
|
-
|
|
118
|
-
expect(description.title).toBeDefined();
|
|
119
|
-
expect(description.body).toBeDefined();
|
|
120
|
-
expect(description.title).toContain('fix');
|
|
121
|
-
expect(description.body).toContain('## Summary');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('includes labels suggestion', () => {
|
|
125
|
-
const fixes: SEOFixSummary[] = [
|
|
126
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
127
|
-
];
|
|
128
|
-
const description = generatePRDescription(fixes);
|
|
129
|
-
|
|
130
|
-
expect(description.labels).toContain('seo');
|
|
131
|
-
expect(description.labels).toContain('automated');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('suggests reviewers if configured', () => {
|
|
135
|
-
const fixes: SEOFixSummary[] = [
|
|
136
|
-
{ category: 'on-page', issues: ['TITLE_MISSING'], filesCount: 1 },
|
|
137
|
-
];
|
|
138
|
-
const config: PRConfig = {
|
|
139
|
-
reviewers: ['alice', 'bob'],
|
|
140
|
-
};
|
|
141
|
-
const description = generatePRDescription(fixes, config);
|
|
142
|
-
|
|
143
|
-
expect(description.reviewers).toEqual(['alice', 'bob']);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe('createPullRequest', () => {
|
|
148
|
-
beforeEach(() => {
|
|
149
|
-
vi.resetAllMocks();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('returns PR URL on success', async () => {
|
|
153
|
-
const mockExec = vi.fn().mockResolvedValue({
|
|
154
|
-
stdout: 'https://github.com/owner/repo/pull/123\n',
|
|
155
|
-
stderr: '',
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const result = await createPullRequest({
|
|
159
|
-
title: 'Test PR',
|
|
160
|
-
body: 'Test body',
|
|
161
|
-
baseBranch: 'main',
|
|
162
|
-
headBranch: 'seo-fixes',
|
|
163
|
-
}, { execAsync: mockExec });
|
|
164
|
-
|
|
165
|
-
expect(result.success).toBe(true);
|
|
166
|
-
expect(result.url).toBe('https://github.com/owner/repo/pull/123');
|
|
167
|
-
expect(result.number).toBe(123);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('handles gh cli errors', async () => {
|
|
171
|
-
const mockExec = vi.fn().mockRejectedValue(new Error('gh: not logged in'));
|
|
172
|
-
|
|
173
|
-
const result = await createPullRequest({
|
|
174
|
-
title: 'Test PR',
|
|
175
|
-
body: 'Test body',
|
|
176
|
-
baseBranch: 'main',
|
|
177
|
-
headBranch: 'seo-fixes',
|
|
178
|
-
}, { execAsync: mockExec });
|
|
179
|
-
|
|
180
|
-
expect(result.success).toBe(false);
|
|
181
|
-
expect(result.error).toContain('not logged in');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('creates branch if needed', async () => {
|
|
185
|
-
const commands: string[] = [];
|
|
186
|
-
const mockExec = vi.fn().mockImplementation((cmd: string) => {
|
|
187
|
-
commands.push(cmd);
|
|
188
|
-
if (cmd.includes('gh pr create')) {
|
|
189
|
-
return Promise.resolve({ stdout: 'https://github.com/owner/repo/pull/456\n', stderr: '' });
|
|
190
|
-
}
|
|
191
|
-
return Promise.resolve({ stdout: '', stderr: '' });
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await createPullRequest({
|
|
195
|
-
title: 'Test PR',
|
|
196
|
-
body: 'Test body',
|
|
197
|
-
baseBranch: 'main',
|
|
198
|
-
headBranch: 'seo-fixes',
|
|
199
|
-
createBranch: true,
|
|
200
|
-
}, { execAsync: mockExec });
|
|
201
|
-
|
|
202
|
-
expect(commands.some(c => c.includes('checkout -b'))).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('supports dry run mode', async () => {
|
|
206
|
-
const mockExec = vi.fn();
|
|
207
|
-
|
|
208
|
-
const result = await createPullRequest({
|
|
209
|
-
title: 'Test PR',
|
|
210
|
-
body: 'Test body',
|
|
211
|
-
baseBranch: 'main',
|
|
212
|
-
headBranch: 'seo-fixes',
|
|
213
|
-
dryRun: true,
|
|
214
|
-
}, { execAsync: mockExec });
|
|
215
|
-
|
|
216
|
-
expect(result.success).toBe(true);
|
|
217
|
-
expect(result.dryRun).toBe(true);
|
|
218
|
-
expect(mockExec).not.toHaveBeenCalledWith(expect.stringContaining('gh pr create'));
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('includes labels in gh command', async () => {
|
|
222
|
-
let capturedCommand = '';
|
|
223
|
-
const mockExec = vi.fn().mockImplementation((cmd: string) => {
|
|
224
|
-
capturedCommand = cmd;
|
|
225
|
-
return Promise.resolve({ stdout: 'https://github.com/owner/repo/pull/789\n', stderr: '' });
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
await createPullRequest({
|
|
229
|
-
title: 'Test PR',
|
|
230
|
-
body: 'Test body',
|
|
231
|
-
baseBranch: 'main',
|
|
232
|
-
headBranch: 'seo-fixes',
|
|
233
|
-
labels: ['seo', 'automated'],
|
|
234
|
-
}, { execAsync: mockExec });
|
|
235
|
-
|
|
236
|
-
expect(capturedCommand).toContain('--label');
|
|
237
|
-
expect(capturedCommand).toContain('seo');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe('integration scenarios', () => {
|
|
242
|
-
it('generates complete PR for typical SEO audit', () => {
|
|
243
|
-
const fixes: SEOFixSummary[] = [
|
|
244
|
-
{
|
|
245
|
-
category: 'on-page',
|
|
246
|
-
issues: ['TITLE_MISSING', 'META_DESC_MISSING', 'H1_MISSING'],
|
|
247
|
-
filesCount: 5
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
category: 'social-meta',
|
|
251
|
-
issues: ['OG_TITLE_MISSING', 'OG_IMAGE_MISSING', 'TWITTER_CARD_MISSING'],
|
|
252
|
-
filesCount: 5
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
category: 'crawlability',
|
|
256
|
-
issues: ['ROBOTS_TXT_MISSING', 'SITEMAP_MISSING'],
|
|
257
|
-
filesCount: 2
|
|
258
|
-
},
|
|
259
|
-
];
|
|
260
|
-
|
|
261
|
-
const description = generatePRDescription(fixes, {
|
|
262
|
-
scoreBefore: 45,
|
|
263
|
-
scoreAfter: 78,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
expect(description.title).toContain('8 issues fixed');
|
|
267
|
-
expect(description.body).toContain('+33');
|
|
268
|
-
expect(description.body).toContain('ROBOTS_TXT_MISSING');
|
|
269
|
-
expect(description.body).toContain('OG_TITLE_MISSING');
|
|
270
|
-
expect(description.labels).toContain('seo');
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('handles single-file schema addition', () => {
|
|
274
|
-
const fixes: SEOFixSummary[] = [
|
|
275
|
-
{ category: 'structured-data', issues: ['SCHEMA_MISSING'], filesCount: 1 },
|
|
276
|
-
];
|
|
277
|
-
|
|
278
|
-
const description = generatePRDescription(fixes);
|
|
279
|
-
|
|
280
|
-
expect(description.title).toContain('feat(schema)');
|
|
281
|
-
expect(description.body).toContain('SCHEMA_MISSING');
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
});
|
package/src/git/pr-helper.ts
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PR Helper
|
|
3
|
-
*
|
|
4
|
-
* Creates pull requests for SEO fixes using GitHub CLI.
|
|
5
|
-
* Generates formatted PR titles, bodies, and handles branch management.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { exec } from 'child_process';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
|
|
11
|
-
const execAsync = promisify(exec);
|
|
12
|
-
|
|
13
|
-
export interface SEOFixSummary {
|
|
14
|
-
category: string;
|
|
15
|
-
issues: string[];
|
|
16
|
-
filesCount: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface PRConfig {
|
|
20
|
-
scoreBefore?: number;
|
|
21
|
-
scoreAfter?: number;
|
|
22
|
-
reviewers?: string[];
|
|
23
|
-
assignees?: string[];
|
|
24
|
-
draft?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface PRDescription {
|
|
28
|
-
title: string;
|
|
29
|
-
body: string;
|
|
30
|
-
labels: string[];
|
|
31
|
-
reviewers?: string[];
|
|
32
|
-
assignees?: string[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface CreatePROptions {
|
|
36
|
-
title: string;
|
|
37
|
-
body: string;
|
|
38
|
-
baseBranch: string;
|
|
39
|
-
headBranch: string;
|
|
40
|
-
createBranch?: boolean;
|
|
41
|
-
labels?: string[];
|
|
42
|
-
reviewers?: string[];
|
|
43
|
-
assignees?: string[];
|
|
44
|
-
draft?: boolean;
|
|
45
|
-
dryRun?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface PRResult {
|
|
49
|
-
success: boolean;
|
|
50
|
-
url?: string;
|
|
51
|
-
number?: number;
|
|
52
|
-
error?: string;
|
|
53
|
-
dryRun?: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface ExecOptions {
|
|
57
|
-
execAsync?: typeof execAsync;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Maps categories to commit types and scopes
|
|
62
|
-
*/
|
|
63
|
-
const CATEGORY_MAPPING: Record<string, { type: string; scope: string; displayName: string }> = {
|
|
64
|
-
'crawlability': { type: 'fix', scope: 'seo', displayName: 'Crawlability' },
|
|
65
|
-
'indexability': { type: 'fix', scope: 'seo', displayName: 'Indexability' },
|
|
66
|
-
'on-page': { type: 'fix', scope: 'meta', displayName: 'On-Page' },
|
|
67
|
-
'content': { type: 'fix', scope: 'content', displayName: 'Content' },
|
|
68
|
-
'links': { type: 'fix', scope: 'links', displayName: 'Links' },
|
|
69
|
-
'images': { type: 'fix', scope: 'images', displayName: 'Images' },
|
|
70
|
-
'structured-data': { type: 'feat', scope: 'schema', displayName: 'Structured Data' },
|
|
71
|
-
'performance': { type: 'perf', scope: 'web', displayName: 'Performance' },
|
|
72
|
-
'security': { type: 'fix', scope: 'security', displayName: 'Security' },
|
|
73
|
-
'mobile': { type: 'fix', scope: 'mobile', displayName: 'Mobile' },
|
|
74
|
-
'international': { type: 'fix', scope: 'i18n', displayName: 'International' },
|
|
75
|
-
'social-meta': { type: 'feat', scope: 'og', displayName: 'Social Meta' },
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Formats a PR title based on the fixes
|
|
80
|
-
*/
|
|
81
|
-
export function formatPRTitle(fixes: SEOFixSummary[]): string {
|
|
82
|
-
if (fixes.length === 0) {
|
|
83
|
-
return 'fix(seo): SEO audit fixes';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const totalIssues = fixes.reduce((sum, f) => sum + f.issues.length, 0);
|
|
87
|
-
const categories = [...new Set(fixes.map(f => f.category))];
|
|
88
|
-
|
|
89
|
-
// Determine type and scope
|
|
90
|
-
let type = 'fix';
|
|
91
|
-
let scope = 'seo';
|
|
92
|
-
|
|
93
|
-
if (categories.length === 1) {
|
|
94
|
-
const mapping = CATEGORY_MAPPING[categories[0]];
|
|
95
|
-
if (mapping) {
|
|
96
|
-
type = mapping.type;
|
|
97
|
-
scope = mapping.scope;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const issueText = totalIssues === 1 ? '1 issue fixed' : `${totalIssues} issues fixed`;
|
|
102
|
-
return `${type}(${scope}): SEO improvements - ${issueText}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Formats the PR body with detailed information
|
|
107
|
-
*/
|
|
108
|
-
export function formatPRBody(
|
|
109
|
-
fixes: SEOFixSummary[],
|
|
110
|
-
options?: { scoreBefore?: number; scoreAfter?: number }
|
|
111
|
-
): string {
|
|
112
|
-
const totalIssues = fixes.reduce((sum, f) => sum + f.issues.length, 0);
|
|
113
|
-
const totalFiles = fixes.reduce((sum, f) => sum + f.filesCount, 0);
|
|
114
|
-
const uniqueFiles = new Set(fixes.flatMap(f => Array(f.filesCount).fill(0))).size || totalFiles;
|
|
115
|
-
|
|
116
|
-
let body = '## Summary\n';
|
|
117
|
-
body += `This PR fixes ${totalIssues} SEO issue${totalIssues === 1 ? '' : 's'} across ${totalFiles} file${totalFiles === 1 ? '' : 's'}.\n\n`;
|
|
118
|
-
|
|
119
|
-
// Score improvement if provided
|
|
120
|
-
if (options?.scoreBefore !== undefined && options?.scoreAfter !== undefined) {
|
|
121
|
-
const improvement = options.scoreAfter - options.scoreBefore;
|
|
122
|
-
const sign = improvement >= 0 ? '+' : '';
|
|
123
|
-
body += `### Score Improvement\n`;
|
|
124
|
-
body += `- Before: ${options.scoreBefore}/100\n`;
|
|
125
|
-
body += `- After: ${options.scoreAfter}/100\n`;
|
|
126
|
-
body += `- Change: **${sign}${improvement}** points\n\n`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Category breakdown
|
|
130
|
-
if (fixes.length > 0) {
|
|
131
|
-
body += '### Issues Fixed\n\n';
|
|
132
|
-
|
|
133
|
-
for (const fix of fixes) {
|
|
134
|
-
const mapping = CATEGORY_MAPPING[fix.category] || { displayName: fix.category };
|
|
135
|
-
body += `**${mapping.displayName}**\n`;
|
|
136
|
-
for (const issue of fix.issues) {
|
|
137
|
-
body += `- \`${issue}\`\n`;
|
|
138
|
-
}
|
|
139
|
-
body += '\n';
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Test plan
|
|
144
|
-
body += '## Test plan\n';
|
|
145
|
-
body += '- [ ] Run SEO audit to verify fixes\n';
|
|
146
|
-
body += '- [ ] Check pages render correctly\n';
|
|
147
|
-
body += '- [ ] Validate structured data (if applicable)\n';
|
|
148
|
-
body += '- [ ] Test social share previews (if OG tags changed)\n';
|
|
149
|
-
body += '\n';
|
|
150
|
-
|
|
151
|
-
// Footer
|
|
152
|
-
body += '---\n';
|
|
153
|
-
body += '🤖 Generated with [SEO Autopilot](https://github.com/seo-autopilot/seo-autopilot)\n';
|
|
154
|
-
|
|
155
|
-
return body;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Generates complete PR description including title, body, and labels
|
|
160
|
-
*/
|
|
161
|
-
export function generatePRDescription(
|
|
162
|
-
fixes: SEOFixSummary[],
|
|
163
|
-
config?: PRConfig
|
|
164
|
-
): PRDescription {
|
|
165
|
-
const title = formatPRTitle(fixes);
|
|
166
|
-
const body = formatPRBody(fixes, {
|
|
167
|
-
scoreBefore: config?.scoreBefore,
|
|
168
|
-
scoreAfter: config?.scoreAfter,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const labels = ['seo', 'automated'];
|
|
172
|
-
|
|
173
|
-
// Add category-specific labels
|
|
174
|
-
const categories = [...new Set(fixes.map(f => f.category))];
|
|
175
|
-
for (const category of categories) {
|
|
176
|
-
const mapping = CATEGORY_MAPPING[category];
|
|
177
|
-
if (mapping && !labels.includes(mapping.scope)) {
|
|
178
|
-
labels.push(mapping.scope);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
title,
|
|
184
|
-
body,
|
|
185
|
-
labels,
|
|
186
|
-
reviewers: config?.reviewers,
|
|
187
|
-
assignees: config?.assignees,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Creates a pull request using GitHub CLI
|
|
193
|
-
*/
|
|
194
|
-
export async function createPullRequest(
|
|
195
|
-
options: CreatePROptions,
|
|
196
|
-
deps?: ExecOptions
|
|
197
|
-
): Promise<PRResult> {
|
|
198
|
-
const run = deps?.execAsync || execAsync;
|
|
199
|
-
|
|
200
|
-
// Dry run mode
|
|
201
|
-
if (options.dryRun) {
|
|
202
|
-
console.log('DRY RUN - Would create PR:');
|
|
203
|
-
console.log(`Title: ${options.title}`);
|
|
204
|
-
console.log(`Base: ${options.baseBranch}`);
|
|
205
|
-
console.log(`Head: ${options.headBranch}`);
|
|
206
|
-
console.log(`Labels: ${options.labels?.join(', ') || 'none'}`);
|
|
207
|
-
console.log('---');
|
|
208
|
-
console.log(options.body);
|
|
209
|
-
console.log('---');
|
|
210
|
-
return { success: true, dryRun: true };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
// Create branch if needed
|
|
215
|
-
if (options.createBranch) {
|
|
216
|
-
await run(`git checkout -b ${options.headBranch}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Build gh pr create command
|
|
220
|
-
let cmd = `gh pr create --title "${options.title.replace(/"/g, '\\"')}"`;
|
|
221
|
-
cmd += ` --base "${options.baseBranch}"`;
|
|
222
|
-
cmd += ` --head "${options.headBranch}"`;
|
|
223
|
-
|
|
224
|
-
// Add labels
|
|
225
|
-
if (options.labels && options.labels.length > 0) {
|
|
226
|
-
cmd += ` --label "${options.labels.join(',')}"`;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Add reviewers
|
|
230
|
-
if (options.reviewers && options.reviewers.length > 0) {
|
|
231
|
-
cmd += ` --reviewer "${options.reviewers.join(',')}"`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Add assignees
|
|
235
|
-
if (options.assignees && options.assignees.length > 0) {
|
|
236
|
-
cmd += ` --assignee "${options.assignees.join(',')}"`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Draft PR
|
|
240
|
-
if (options.draft) {
|
|
241
|
-
cmd += ' --draft';
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Add body using heredoc
|
|
245
|
-
cmd += ` --body "$(cat <<'EOF'\n${options.body}\nEOF\n)"`;
|
|
246
|
-
|
|
247
|
-
const { stdout } = await run(cmd);
|
|
248
|
-
const url = stdout.trim();
|
|
249
|
-
|
|
250
|
-
// Extract PR number from URL
|
|
251
|
-
const numberMatch = url.match(/\/pull\/(\d+)/);
|
|
252
|
-
const number = numberMatch ? parseInt(numberMatch[1], 10) : undefined;
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
success: true,
|
|
256
|
-
url,
|
|
257
|
-
number,
|
|
258
|
-
};
|
|
259
|
-
} catch (error) {
|
|
260
|
-
return {
|
|
261
|
-
success: false,
|
|
262
|
-
error: (error as Error).message,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Generates a branch name for SEO fixes
|
|
269
|
-
*/
|
|
270
|
-
export function generateBranchName(prefix = 'seo-fixes'): string {
|
|
271
|
-
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
272
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
273
|
-
return `${prefix}-${timestamp}-${random}`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Checks if gh CLI is available and authenticated
|
|
278
|
-
*/
|
|
279
|
-
export async function checkGitHubCLI(deps?: ExecOptions): Promise<{
|
|
280
|
-
available: boolean;
|
|
281
|
-
authenticated: boolean;
|
|
282
|
-
error?: string;
|
|
283
|
-
}> {
|
|
284
|
-
const run = deps?.execAsync || execAsync;
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
// Check if gh is installed
|
|
288
|
-
await run('gh --version');
|
|
289
|
-
|
|
290
|
-
// Check if authenticated
|
|
291
|
-
await run('gh auth status');
|
|
292
|
-
|
|
293
|
-
return { available: true, authenticated: true };
|
|
294
|
-
} catch (error) {
|
|
295
|
-
const message = (error as Error).message;
|
|
296
|
-
|
|
297
|
-
if (message.includes('command not found') || message.includes('not recognized')) {
|
|
298
|
-
return { available: false, authenticated: false, error: 'GitHub CLI (gh) is not installed' };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (message.includes('not logged') || message.includes('auth')) {
|
|
302
|
-
return { available: true, authenticated: false, error: 'GitHub CLI is not authenticated' };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return { available: false, authenticated: false, error: message };
|
|
306
|
-
}
|
|
307
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Agent runtime for SEO Autopilot
|
|
2
|
-
|
|
3
|
-
// Types
|
|
4
|
-
export * from './types.js';
|
|
5
|
-
|
|
6
|
-
// Comprehensive Audit Module (Ahrefs-level checks)
|
|
7
|
-
export * from './audit/index.js';
|
|
8
|
-
|
|
9
|
-
// Keyword Research Module
|
|
10
|
-
export * from './keywords/index.js';
|
|
11
|
-
|
|
12
|
-
// Content Analysis & Optimization Module
|
|
13
|
-
export * from './content/index.js';
|
|
14
|
-
|
|
15
|
-
// Tracking & Monitoring Module
|
|
16
|
-
export * from './tracking/index.js';
|
|
17
|
-
|
|
18
|
-
// Loader
|
|
19
|
-
export { loadAgent, loadAgentByName, interpolatePrompt } from './loader.js';
|
|
20
|
-
|
|
21
|
-
// Executor
|
|
22
|
-
export { executeAgent, runDirectAnalysis } from './executor.js';
|
|
23
|
-
export type { ExecuteOptions, ExecutionResult } from './executor.js';
|
|
24
|
-
|
|
25
|
-
// Fix generator
|
|
26
|
-
export { generateFixes, applyFixes } from './fixer.js';
|
|
27
|
-
export type { FixGeneratorOptions, GeneratedFix } from './fixer.js';
|
|
28
|
-
export * from './fixer/framework-fixes.js';
|
|
29
|
-
// Schemas are exported from ./fixer/schemas.js - import directly to avoid conflicts
|
|
30
|
-
// with generateFAQJsonLd and other duplicates in framework-fixes
|
|
31
|
-
export { Schemas } from './fixer/schemas.js';
|
|
32
|
-
|
|
33
|
-
// Tools
|
|
34
|
-
export { tools } from './tools/index.js';
|
|
35
|
-
export {
|
|
36
|
-
crawlUrl,
|
|
37
|
-
extractMeta,
|
|
38
|
-
analyzeHeadings,
|
|
39
|
-
extractImages,
|
|
40
|
-
extractLinks,
|
|
41
|
-
extractSchema,
|
|
42
|
-
checkRobots,
|
|
43
|
-
checkSitemap,
|
|
44
|
-
readFile,
|
|
45
|
-
writeFile,
|
|
46
|
-
listFiles,
|
|
47
|
-
detectFramework,
|
|
48
|
-
findHtmlEntry,
|
|
49
|
-
findPageFiles,
|
|
50
|
-
analyzeUrl,
|
|
51
|
-
} from './tools/index.js';
|
|
52
|
-
|
|
53
|
-
// Git integration (conventional commits, co-authoring)
|
|
54
|
-
export * from './git/index.js';
|
|
55
|
-
|
|
56
|
-
// Scheduler (scheduled audits, alerts)
|
|
57
|
-
export * from './scheduler/index.js';
|
|
58
|
-
|
|
59
|
-
// Reports (HTML/PDF generation, white-label)
|
|
60
|
-
export * from './reports/index.js';
|
|
61
|
-
|
|
62
|
-
// GEO (Generative Engine Optimization) tracking
|
|
63
|
-
export * from './geo/index.js';
|
|
64
|
-
|
|
65
|
-
// Keyword Rank Tracking
|
|
66
|
-
export * from './ranking/index.js';
|
|
67
|
-
|
|
68
|
-
// Advanced Analyzers (GEO, CWV, Security, Schema, Images, Links, Mobile)
|
|
69
|
-
export * as analyzers from './analyzers/index.js';
|
|
70
|
-
|
|
71
|
-
// Framework Detection & SEO Recipes
|
|
72
|
-
// Note: Export as namespace to avoid conflict with tools/detectFramework
|
|
73
|
-
export * as frameworks from './frameworks/index.js';
|
|
74
|
-
|
|
75
|
-
// WASM Adapter (sync audit for embedded execution)
|
|
76
|
-
export { runSyncAudit } from './wasm-adapter.js';
|