seo-gravity-mcp 1.0.2 → 1.1.0
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 +72 -105
- package/dist/adapters/adapterRegistry.d.ts +7 -0
- package/dist/adapters/adapterRegistry.js +30 -0
- package/dist/adapters/astroAdapter.d.ts +17 -0
- package/dist/adapters/astroAdapter.js +191 -0
- package/dist/adapters/nextAppAdapter.d.ts +17 -0
- package/dist/adapters/nextAppAdapter.js +265 -0
- package/dist/adapters/nextPagesAdapter.d.ts +17 -0
- package/dist/adapters/nextPagesAdapter.js +190 -0
- package/dist/adapters/remixAdapter.d.ts +16 -0
- package/dist/adapters/remixAdapter.js +118 -0
- package/dist/adapters/staticAdapter.d.ts +16 -0
- package/dist/adapters/staticAdapter.js +117 -0
- package/dist/adapters/svelteKitAdapter.d.ts +16 -0
- package/dist/adapters/svelteKitAdapter.js +122 -0
- package/dist/adapters/types.d.ts +33 -0
- package/dist/adapters/types.js +1 -0
- package/dist/adapters/viteReactAdapter.d.ts +16 -0
- package/dist/adapters/viteReactAdapter.js +172 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +97 -0
- package/dist/providers/providerRegistry.d.ts +65 -0
- package/dist/providers/providerRegistry.js +64 -0
- package/dist/providers/types.d.ts +22 -0
- package/dist/providers/types.js +1 -0
- package/dist/test.js +103 -132
- package/dist/tools/orchestration.js +23 -21
- package/dist/types/canonical.d.ts +64 -0
- package/dist/types/canonical.js +1 -0
- package/dist/types/findings.d.ts +22 -0
- package/dist/types/findings.js +1 -1
- package/dist/types/seo.d.ts +24 -0
- package/dist/utils/astLocator.d.ts +14 -0
- package/dist/utils/astLocator.js +137 -0
- package/dist/utils/cacheManager.d.ts +15 -0
- package/dist/utils/cacheManager.js +37 -0
- package/dist/utils/experimentEngine.d.ts +26 -0
- package/dist/utils/experimentEngine.js +41 -0
- package/dist/utils/findingEngine.d.ts +2 -1
- package/dist/utils/findingEngine.js +1 -0
- package/dist/utils/gitDiffEngine.d.ts +19 -0
- package/dist/utils/gitDiffEngine.js +72 -0
- package/dist/utils/opportunityEngine.d.ts +19 -0
- package/dist/utils/opportunityEngine.js +61 -0
- package/dist/utils/projectScanner.d.ts +4 -2
- package/dist/utils/projectScanner.js +14 -346
- package/dist/utils/snapshotEngine.d.ts +3 -1
- package/dist/utils/snapshotEngine.js +213 -93
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
# 🚀 SEO Gravity
|
|
1
|
+
# 🚀 SEO Gravity (v1.1.0)
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/seo-gravity-mcp)
|
|
6
6
|
[](https://www.npmjs.com/package/seo-gravity-mcp)
|
|
7
7
|

|
|
8
|
+

|
|
8
9
|

|
|
9
|
-

|
|
10
|
-

|
|
11
10
|

|
|
12
11
|
|
|
13
|
-
**The
|
|
12
|
+
**The Enterprise SEO Engineering Layer for AI Coding Agents & CI/CD Pipelines.**
|
|
14
13
|
|
|
15
|
-
[⚡ Instant Start](#-instant-start) • [
|
|
14
|
+
[⚡ Instant Start](#-instant-start) • [💻 CLI & CI/CD](#-command-line--cicd-runner) • [🏛 5-Layer Architecture](#-5-layer-system-architecture) • [🧩 Framework Adapters](#-modular-framework-adapters) • [🛠 Tool Catalog (35 Tools)](#-complete-tool-catalog) • [📄 License](#-license)
|
|
16
15
|
|
|
17
16
|
</div>
|
|
18
17
|
|
|
@@ -20,67 +19,87 @@
|
|
|
20
19
|
|
|
21
20
|
## 🌟 What is SEO Gravity?
|
|
22
21
|
|
|
23
|
-
**SEO Gravity**
|
|
22
|
+
**SEO Gravity** is an **SEO engineering infrastructure layer** designed specifically for AI coding agents (Antigravity, Cursor, Claude Code, Windsurf, Cline, Roo Code, etc.) and automated CI/CD pipelines.
|
|
24
23
|
|
|
25
|
-
Instead of
|
|
24
|
+
Instead of operating as a passive SEO reporting tool, SEO Gravity connects **rendered web observations** directly to **source code AST nodes and line numbers** across modern web frameworks (Next.js App/Pages Router, Astro, Vite/React, Remix, SvelteKit), generates actionable code fix blueprints, and enforces invariant-based regression checks.
|
|
26
25
|
|
|
27
26
|
```text
|
|
28
27
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
29
28
|
│ 1. DISCOVER │ ──▶ │ 2. OBSERVE │ ──▶ │ 3. DIAGNOSE │ ──▶ │ 4. FIX │ ──▶ │ 5. VERIFY │
|
|
30
|
-
│ Framework │ │ Live/Dev SSR │ │
|
|
31
|
-
│
|
|
32
|
-
│ Source Map │ │
|
|
29
|
+
│ Framework │ │ Live/Dev SSR │ │ AST Line Nos │ │ AI Modifies │ │ Invariant │
|
|
30
|
+
│ Adapters │ │ Crawl Graph │ │ Prioritized │ │ Components │ │ Regression │
|
|
31
|
+
│ Source Map │ │ Provenance │ │ Action Plan │ │ Snippets │ │ Git Stamping │
|
|
33
32
|
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
|
34
33
|
```
|
|
35
34
|
|
|
36
35
|
---
|
|
37
36
|
|
|
38
|
-
##
|
|
37
|
+
## 🏛 5-Layer System Architecture
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
```text
|
|
40
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
41
|
+
│ 1. AGENT & CLI INTERFACE │
|
|
42
|
+
│ MCP Server (35 tools) + Standalone CLI (`seo-gravity`) │
|
|
43
|
+
├─────────────────────────────────────────────────────────────┤
|
|
44
|
+
│ 2. INTELLIGENCE & REASONING LAYER │
|
|
45
|
+
│ Root-cause diagnosis, deterministic priority, fix plans │
|
|
46
|
+
├─────────────────────────────────────────────────────────────┤
|
|
47
|
+
│ 3. ANALYSIS & INVARIANT LAYER │
|
|
48
|
+
│ SEO Invariants (HTTP 200, canonical, schema, noindex) │
|
|
49
|
+
├─────────────────────────────────────────────────────────────┤
|
|
50
|
+
│ 4. OBSERVATION LAYER (Factual Raw State) │
|
|
51
|
+
│ Observations ≠ Findings; AST inspection, headers, DOM │
|
|
52
|
+
├─────────────────────────────────────────────────────────────┤
|
|
53
|
+
│ 5. PROVIDER & ADAPTER LAYER │
|
|
54
|
+
│ Framework Adapters (Next, Astro, Vite), Providers, Cache │
|
|
55
|
+
└─────────────────────────────────────────────────────────────┘
|
|
56
|
+
```
|
|
46
57
|
|
|
47
58
|
---
|
|
48
59
|
|
|
49
|
-
##
|
|
60
|
+
## 🧩 Modular Framework Adapters
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
| Framework Adapter | Detection | Route Discovery | AST Inspection |
|
|
63
|
+
| :--- | :--- | :--- | :--- |
|
|
64
|
+
| **`NextAppAdapter`** | `next` + `app/` | App Router hierarchy, route groups `(group)`, dynamic `[slug]` | Pinpoints `metadata` export & `generateMetadata()` lines |
|
|
65
|
+
| **`NextPagesAdapter`** | `next` + `pages/` | Pages router hierarchy, dynamic `[id]` | Scans `<Head>` & `<NextSeo>` JSX components |
|
|
66
|
+
| **`AstroAdapter`** | `astro` + `src/pages` | `.astro`, `.md`, `.mdx` pages | Scans Astro frontmatter & `<title>` tags |
|
|
67
|
+
| **`ViteReactAdapter`** | `vite` / `react` | `index.html` + React Router routes | Scans `<Helmet>` & route definition trees |
|
|
68
|
+
| **`RemixAdapter`** | `@remix-run/react` | `app/routes/` | Inspects `meta` and `links` export functions |
|
|
69
|
+
| **`SvelteKitAdapter`** | `@sveltejs/kit` | `src/routes/` (`+page.svelte`) | Scans `<svelte:head>` components |
|
|
70
|
+
| **`StaticAdapter`** | Static HTML files | Recursive HTML directory traversal | Direct DOM / meta element inspection |
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
npx -y seo-gravity-mcp
|
|
55
|
-
```
|
|
72
|
+
---
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
## 💻 Command-Line & CI/CD Runner
|
|
75
|
+
|
|
76
|
+
SEO Gravity includes a standalone CLI binary (`seo-gravity`) for direct terminal use and GitHub Actions / CI/CD quality gates:
|
|
58
77
|
|
|
59
78
|
```bash
|
|
60
|
-
|
|
79
|
+
# 1. Audit project
|
|
80
|
+
npx seo-gravity-mcp audit --project ./my-app
|
|
81
|
+
|
|
82
|
+
# 2. Create baseline snapshot
|
|
83
|
+
npx seo-gravity-mcp snapshot --project ./my-app --output baseline.json
|
|
84
|
+
|
|
85
|
+
# 3. Check for SEO regressions in CI/CD PR gate (Exits with 0 or 1)
|
|
86
|
+
npx seo-gravity-mcp check --project ./my-app --baseline baseline.json
|
|
87
|
+
|
|
88
|
+
# 4. Targeted Git differential audit on modified routes only
|
|
89
|
+
npx seo-gravity-mcp diff --project ./my-app --base-ref origin/main
|
|
61
90
|
```
|
|
62
91
|
|
|
63
92
|
---
|
|
64
93
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
### 1. 🟣 Claude Desktop
|
|
68
|
-
Add to `claude_desktop_config.json`:
|
|
94
|
+
## ⚡ Instant Start (MCP Server)
|
|
69
95
|
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
"mcpServers": {
|
|
73
|
-
"seo-gravity": {
|
|
74
|
-
"command": "npx",
|
|
75
|
-
"args": ["-y", "seo-gravity-mcp"]
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
96
|
+
```bash
|
|
97
|
+
npx -y seo-gravity-mcp
|
|
79
98
|
```
|
|
80
99
|
|
|
81
|
-
###
|
|
82
|
-
Add to `.cursor/mcp.json`:
|
|
100
|
+
### AI Client Configurations
|
|
83
101
|
|
|
102
|
+
#### Cursor (`.cursor/mcp.json`)
|
|
84
103
|
```json
|
|
85
104
|
{
|
|
86
105
|
"mcpServers": {
|
|
@@ -92,9 +111,7 @@ Add to `.cursor/mcp.json`:
|
|
|
92
111
|
}
|
|
93
112
|
```
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
Add to `mcp_config.json`:
|
|
97
|
-
|
|
114
|
+
#### Claude Desktop (`claude_desktop_config.json`)
|
|
98
115
|
```json
|
|
99
116
|
{
|
|
100
117
|
"mcpServers": {
|
|
@@ -108,78 +125,28 @@ Add to `mcp_config.json`:
|
|
|
108
125
|
|
|
109
126
|
---
|
|
110
127
|
|
|
111
|
-
## 🛠 Complete Tool Catalog
|
|
128
|
+
## 🛠 Complete Tool Catalog
|
|
112
129
|
|
|
113
130
|
### Layer 0: Agent Orchestration & Remediation (Flagship)
|
|
114
131
|
| Tool Name | Operational Class | Description |
|
|
115
132
|
| :--- | :--- | :--- |
|
|
116
133
|
| `seo_project_audit` | **Planning / Audit** | Scans workspace framework, discovers routes, correlates source files, builds crawl graph, and computes multidimensional SEO health. |
|
|
117
|
-
| `seo_diagnose` | **Planning / Diagnose** | Deep root-cause inspection linking an observed issue/URL back to specific source code
|
|
134
|
+
| `seo_diagnose` | **Planning / Diagnose** | Deep root-cause AST inspection linking an observed issue/URL back to specific source code line ranges with ready-to-use fix blueprints. |
|
|
118
135
|
| `seo_prioritize` | **Planning / Strategy** | Ranks findings into Quick Wins, Critical Blockers, and Architectural Improvements using `(Impact × Confidence × Reach) / Effort`. |
|
|
119
136
|
| `seo_fix_plan` | **Planning / Execution** | Generates an end-to-end code modification and verification plan for AI coding agents. |
|
|
120
|
-
| `seo_snapshot_create` | **Verification** | Creates and saves a project SEO baseline snapshot adhering to
|
|
121
|
-
| `seo_snapshot_compare` | **Verification** |
|
|
137
|
+
| `seo_snapshot_create` | **Verification** | Creates and saves a project SEO baseline snapshot with Git commit stamping adhering to schema `seo.gravity/v1`. |
|
|
138
|
+
| `seo_snapshot_compare` | **Verification** | Invariant-based diffing of two snapshots to isolate resolved findings, new regressions, and multidimensional score deltas. |
|
|
122
139
|
| `seo_regression_check` | **Verification / CI** | Automated pass/fail regression check with alert messages for CI/CD gates and PR bots. |
|
|
123
140
|
|
|
124
|
-
###
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### Layer 2: Generative Engine Optimization (GEO & AEO)
|
|
134
|
-
| Tool Name | Description |
|
|
135
|
-
| :--- | :--- |
|
|
136
|
-
| `seo_geo_ai_readiness_audit` | Audits content for AI Overviews & Perplexity citation readiness with structured retrieval signals and evidence tiers. |
|
|
137
|
-
| `seo_llms_txt_generate` | Generates standard `/llms.txt` and `/llms-full.txt` markdown context files for AI search bots. |
|
|
138
|
-
| `seo_ai_bots_robots_audit` | Audits robots.txt permissions specifically for AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended). |
|
|
139
|
-
|
|
140
|
-
### Layer 3: Information Gain & E-E-A-T
|
|
141
|
-
| Tool Name | Description |
|
|
142
|
-
| :--- | :--- |
|
|
143
|
-
| `seo_information_gain_score` | Quantifies content novelty vs top 10 SERPs to prevent generic AI fluff penalties. |
|
|
144
|
-
| `seo_eeat_audit` | Audits Google E-E-A-T trust signals (Person schema, author bylines, sameAs Wikidata/LinkedIn, publish/modified dates). |
|
|
145
|
-
|
|
146
|
-
### Layer 4: On-Page & Content Strategy
|
|
147
|
-
| Tool Name | Description |
|
|
148
|
-
| :--- | :--- |
|
|
149
|
-
| `seo_onpage_audit` | Deep on-page audit of pixel width, meta CTR, heading hierarchy, image alt, and slug optimization. |
|
|
150
|
-
| `seo_content_brief_generate` | Generates data-backed Content Outline & Brief with target word count, H1/H2/H3 structure, and FAQs. |
|
|
151
|
-
| `seo_readability_score` | Computes Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog index. |
|
|
152
|
-
|
|
153
|
-
### Layer 5: Technical SEO & JavaScript Hydration
|
|
154
|
-
| Tool Name | Description |
|
|
155
|
-
| :--- | :--- |
|
|
156
|
-
| `seo_technical_audit` | Inspects HTTP status codes, redirect chains, canonical consistency, meta robots (`noindex` leaks), and SSL. |
|
|
157
|
-
| `seo_js_rendering_diff` | Compares raw server HTML vs hydrated client DOM (JavaScript SEO) to detect client-only content or broken tags. |
|
|
158
|
-
| `seo_robots_txt_validate` | Validates bot crawlability against specific paths and User-Agents. |
|
|
159
|
-
| `seo_sitemap_inspect` | Validates XML sitemaps, sitemap indexes, lastmod timestamps, and URL limit constraints. |
|
|
160
|
-
| `seo_internal_links_analyze` | Analyzes internal link equity distribution, anchor texts, generic anchors, and nofollow flags. |
|
|
161
|
-
|
|
162
|
-
### Layer 6: Keyword Research & Intent
|
|
163
|
-
| Tool Name | Description |
|
|
164
|
-
| :--- | :--- |
|
|
165
|
-
| `seo_keyword_suggestions` | Generates keyword suggestions using Google Autocomplete and Alphabet Soup expansion. |
|
|
166
|
-
| `seo_questions_find` | Finds question queries asked by users across Google (Who, What, Where, When, Why, How, Can). |
|
|
167
|
-
| `seo_keyword_cluster` | Clusters keyword lists into Topic Pillars and Supporting Articles using semantic similarity. |
|
|
168
|
-
| `seo_search_intent_classify` | Classifies keywords into Informational, Navigational, Commercial, or Transactional intent. |
|
|
169
|
-
|
|
170
|
-
### Layer 7: Schema & Entity Graph
|
|
171
|
-
| Tool Name | Description |
|
|
172
|
-
| :--- | :--- |
|
|
173
|
-
| `seo_entity_salience_map` | Extracts named entities, computes salience scores, and generates SPO relationship triples. |
|
|
174
|
-
| `seo_schema_generate` | Generates validated Schema.org JSON-LD scripts (Article, FAQPage, Product, LocalBusiness, Organization). |
|
|
175
|
-
| `seo_schema_validate` | Validates structured data against Schema.org and Google Rich Result criteria. |
|
|
176
|
-
|
|
177
|
-
### Layer 8: Performance & Maintenance
|
|
178
|
-
| Tool Name | Description |
|
|
179
|
-
| :--- | :--- |
|
|
180
|
-
| `seo_pagespeed_audit` | Checks Core Web Vitals (LCP, FCP, CLS, TTFB) with performance optimization fixes. |
|
|
181
|
-
| `seo_indexnow_submit` | Submits newly created or updated URLs directly to Bing & Yandex via the IndexNow API protocol. |
|
|
182
|
-
| `seo_content_decay_audit` | Scans content for freshness decay (stale year references, outdated stats, broken external links). |
|
|
141
|
+
### Layers 1–8: Specialized SEO & GEO Engines
|
|
142
|
+
- **Layer 1 (SERP & Competitors)**: `seo_serp_analyze`, `seo_competitor_content_gap`, `seo_competitor_profile`, `seo_competitor_diff`, `seo_forum_discussions_pulse`
|
|
143
|
+
- **Layer 2 (GEO & AI Search)**: `seo_geo_ai_readiness_audit`, `seo_llms_txt_generate`, `seo_ai_bots_robots_audit`
|
|
144
|
+
- **Layer 3 (Info Gain & E-E-A-T)**: `seo_information_gain_score`, `seo_eeat_audit`
|
|
145
|
+
- **Layer 4 (On-Page)**: `seo_onpage_audit`, `seo_content_brief_generate`, `seo_readability_score`
|
|
146
|
+
- **Layer 5 (Technical SEO)**: `seo_technical_audit`, `seo_js_rendering_diff`, `seo_robots_txt_validate`, `seo_sitemap_inspect`, `seo_internal_links_analyze`
|
|
147
|
+
- **Layer 6 (Keywords & Intent)**: `seo_keyword_suggestions`, `seo_questions_find`, `seo_keyword_cluster`, `seo_search_intent_classify`
|
|
148
|
+
- **Layer 7 (Schema & Entity)**: `seo_entity_salience_map`, `seo_schema_generate`, `seo_schema_validate`
|
|
149
|
+
- **Layer 8 (Speed & Maintenance)**: `seo_pagespeed_audit`, `seo_indexnow_submit`, `seo_content_decay_audit`
|
|
183
150
|
|
|
184
151
|
---
|
|
185
152
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FrameworkAdapter } from './types.js';
|
|
2
|
+
export declare class AdapterRegistry {
|
|
3
|
+
private adapters;
|
|
4
|
+
getAdapterForProject(projectDir: string): FrameworkAdapter;
|
|
5
|
+
getAdapterById(id: string): FrameworkAdapter | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare const defaultAdapterRegistry: AdapterRegistry;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NextAppAdapter } from './nextAppAdapter.js';
|
|
2
|
+
import { NextPagesAdapter } from './nextPagesAdapter.js';
|
|
3
|
+
import { AstroAdapter } from './astroAdapter.js';
|
|
4
|
+
import { ViteReactAdapter } from './viteReactAdapter.js';
|
|
5
|
+
import { RemixAdapter } from './remixAdapter.js';
|
|
6
|
+
import { SvelteKitAdapter } from './svelteKitAdapter.js';
|
|
7
|
+
import { StaticAdapter } from './staticAdapter.js';
|
|
8
|
+
export class AdapterRegistry {
|
|
9
|
+
adapters = [
|
|
10
|
+
new NextAppAdapter(),
|
|
11
|
+
new NextPagesAdapter(),
|
|
12
|
+
new AstroAdapter(),
|
|
13
|
+
new RemixAdapter(),
|
|
14
|
+
new SvelteKitAdapter(),
|
|
15
|
+
new ViteReactAdapter(),
|
|
16
|
+
new StaticAdapter() // fallback
|
|
17
|
+
];
|
|
18
|
+
getAdapterForProject(projectDir) {
|
|
19
|
+
for (const adapter of this.adapters) {
|
|
20
|
+
if (adapter.detect(projectDir)) {
|
|
21
|
+
return adapter;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return this.adapters[this.adapters.length - 1]; // fallback static
|
|
25
|
+
}
|
|
26
|
+
getAdapterById(id) {
|
|
27
|
+
return this.adapters.find(a => a.id === id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const defaultAdapterRegistry = new AdapterRegistry();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FrameworkAdapter, MetadataLocationInfo, CanonicalLocationInfo, SchemaLocationInfo } from './types.js';
|
|
2
|
+
import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../types/findings.js';
|
|
3
|
+
export declare class AstroAdapter implements FrameworkAdapter {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
detect(projectDir: string): boolean;
|
|
7
|
+
getProjectInfo(projectDir: string): ProjectFrameworkInfo;
|
|
8
|
+
discoverRoutes(projectDir: string): DiscoveredRoute[];
|
|
9
|
+
mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
|
|
10
|
+
findMetadataImplementation(projectDir: string, route: DiscoveredRoute): Promise<MetadataLocationInfo>;
|
|
11
|
+
findCanonicalDeclaration(projectDir: string, route: DiscoveredRoute): Promise<CanonicalLocationInfo>;
|
|
12
|
+
findSchemaDeclaration(projectDir: string, route: DiscoveredRoute): Promise<SchemaLocationInfo>;
|
|
13
|
+
findRobotsConfig(projectDir: string): string | null;
|
|
14
|
+
findSitemapConfig(projectDir: string): string | null;
|
|
15
|
+
findLlmsTxt(projectDir: string): string | null;
|
|
16
|
+
private extractDynamicParams;
|
|
17
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export class AstroAdapter {
|
|
4
|
+
id = 'astro';
|
|
5
|
+
name = 'Astro Framework';
|
|
6
|
+
detect(projectDir) {
|
|
7
|
+
const hasConfig = fs.existsSync(path.join(projectDir, 'astro.config.mjs')) ||
|
|
8
|
+
fs.existsSync(path.join(projectDir, 'astro.config.ts'));
|
|
9
|
+
if (hasConfig)
|
|
10
|
+
return true;
|
|
11
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
12
|
+
if (fs.existsSync(pkgPath)) {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
15
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
16
|
+
return Boolean(deps['astro']);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
getProjectInfo(projectDir) {
|
|
23
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
24
|
+
let pkg = {};
|
|
25
|
+
if (fs.existsSync(pkgPath)) {
|
|
26
|
+
try {
|
|
27
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
const hasSrcPages = fs.existsSync(path.join(projectDir, 'src/pages'));
|
|
32
|
+
const routesDir = hasSrcPages ? 'src/pages' : 'pages';
|
|
33
|
+
return {
|
|
34
|
+
framework: 'astro',
|
|
35
|
+
name: pkg.name || path.basename(projectDir),
|
|
36
|
+
version: pkg.dependencies?.astro || pkg.devDependencies?.astro,
|
|
37
|
+
packageManager: fs.existsSync(path.join(projectDir, 'pnpm-lock.yaml'))
|
|
38
|
+
? 'pnpm'
|
|
39
|
+
: fs.existsSync(path.join(projectDir, 'yarn.lock'))
|
|
40
|
+
? 'yarn'
|
|
41
|
+
: 'npm',
|
|
42
|
+
hasTypeScript: fs.existsSync(path.join(projectDir, 'tsconfig.json')),
|
|
43
|
+
hasSitemapConfig: Boolean(this.findSitemapConfig(projectDir)),
|
|
44
|
+
hasRobotsConfig: Boolean(this.findRobotsConfig(projectDir)),
|
|
45
|
+
hasLlmsTxt: Boolean(this.findLlmsTxt(projectDir)),
|
|
46
|
+
rootDir: path.resolve(projectDir),
|
|
47
|
+
routesDir,
|
|
48
|
+
devCommand: 'npm run dev',
|
|
49
|
+
defaultDevPort: 4321
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
discoverRoutes(projectDir) {
|
|
53
|
+
const info = this.getProjectInfo(projectDir);
|
|
54
|
+
const fullRoutesDir = path.join(projectDir, info.routesDir || 'src/pages');
|
|
55
|
+
const discovered = [];
|
|
56
|
+
if (!fs.existsSync(fullRoutesDir))
|
|
57
|
+
return discovered;
|
|
58
|
+
const scanDir = (currentDir, routePrefix) => {
|
|
59
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const entryPath = path.join(currentDir, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
const nextPrefix = routePrefix === '/' ? `/${entry.name}` : `${routePrefix}/${entry.name}`;
|
|
64
|
+
scanDir(entryPath, nextPrefix);
|
|
65
|
+
}
|
|
66
|
+
else if (entry.isFile() && /\.(astro|md|mdx)$/.test(entry.name)) {
|
|
67
|
+
const baseName = entry.name.replace(/\.(astro|md|mdx)$/, '');
|
|
68
|
+
let routePath = routePrefix;
|
|
69
|
+
if (baseName !== 'index') {
|
|
70
|
+
routePath = routePrefix === '/' ? `/${baseName}` : `${routePrefix}/${baseName}`;
|
|
71
|
+
}
|
|
72
|
+
if (routePath === '')
|
|
73
|
+
routePath = '/';
|
|
74
|
+
const relFile = path.relative(projectDir, entryPath).replace(/\\/g, '/');
|
|
75
|
+
const content = fs.readFileSync(entryPath, 'utf-8');
|
|
76
|
+
const dynamicParams = this.extractDynamicParams(routePath);
|
|
77
|
+
discovered.push({
|
|
78
|
+
routePath,
|
|
79
|
+
sourceFilePath: relFile,
|
|
80
|
+
isDynamic: dynamicParams.length > 0,
|
|
81
|
+
dynamicParams,
|
|
82
|
+
routeType: 'page',
|
|
83
|
+
hasMetadataExport: /title\s*=|description\s*=|astro:head|<title/.test(content),
|
|
84
|
+
hasGenerateMetadata: false,
|
|
85
|
+
hasSchemaMarkup: /application\/ld\+json/.test(content),
|
|
86
|
+
hasHeadComponent: /<head|astro:head|<title/.test(content),
|
|
87
|
+
isClientComponent: false
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
scanDir(fullRoutesDir, '/');
|
|
93
|
+
return discovered;
|
|
94
|
+
}
|
|
95
|
+
mapRouteToSource(targetUrl, routes) {
|
|
96
|
+
let urlPath = targetUrl;
|
|
97
|
+
try {
|
|
98
|
+
if (targetUrl.startsWith('http')) {
|
|
99
|
+
urlPath = new URL(targetUrl).pathname;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
urlPath = targetUrl;
|
|
104
|
+
}
|
|
105
|
+
urlPath = urlPath.replace(/\/$/, '') || '/';
|
|
106
|
+
for (const r of routes) {
|
|
107
|
+
const clean = r.routePath.replace(/\/$/, '') || '/';
|
|
108
|
+
if (clean === urlPath) {
|
|
109
|
+
return {
|
|
110
|
+
urlPath,
|
|
111
|
+
matchedRoute: r,
|
|
112
|
+
sourceFilePath: r.sourceFilePath,
|
|
113
|
+
confidence: 1.0,
|
|
114
|
+
resolutionMethod: 'exact_match'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
urlPath,
|
|
120
|
+
confidence: 0.0,
|
|
121
|
+
resolutionMethod: 'unmapped'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async findMetadataImplementation(projectDir, route) {
|
|
125
|
+
const fullPath = path.join(projectDir, route.sourceFilePath);
|
|
126
|
+
if (!fs.existsSync(fullPath))
|
|
127
|
+
return { hasMetadata: false, type: 'none' };
|
|
128
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
129
|
+
const hasMeta = /<title\b|<meta\s+name=["']description["']/i.test(content) || /title\s*:/i.test(content);
|
|
130
|
+
return {
|
|
131
|
+
hasMetadata: hasMeta,
|
|
132
|
+
type: hasMeta ? 'jsx_head' : 'none'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async findCanonicalDeclaration(projectDir, route) {
|
|
136
|
+
const fullPath = path.join(projectDir, route.sourceFilePath);
|
|
137
|
+
if (!fs.existsSync(fullPath))
|
|
138
|
+
return { hasCanonical: false, type: 'none' };
|
|
139
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
140
|
+
const hasCanonical = /rel=["']canonical["']/i.test(content);
|
|
141
|
+
return {
|
|
142
|
+
hasCanonical,
|
|
143
|
+
type: hasCanonical ? 'link_jsx_tag' : 'none'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async findSchemaDeclaration(projectDir, route) {
|
|
147
|
+
const fullPath = path.join(projectDir, route.sourceFilePath);
|
|
148
|
+
if (!fs.existsSync(fullPath))
|
|
149
|
+
return { hasSchema: false, typesFound: [] };
|
|
150
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
151
|
+
const hasSchema = /application\/ld\+json/i.test(content);
|
|
152
|
+
return {
|
|
153
|
+
hasSchema,
|
|
154
|
+
typesFound: hasSchema ? ['JSON-LD'] : []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
findRobotsConfig(projectDir) {
|
|
158
|
+
const paths = ['public/robots.txt', 'robots.txt'];
|
|
159
|
+
for (const p of paths) {
|
|
160
|
+
if (fs.existsSync(path.join(projectDir, p)))
|
|
161
|
+
return p;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
findSitemapConfig(projectDir) {
|
|
166
|
+
const paths = ['public/sitemap.xml', 'sitemap.xml', 'src/pages/sitemap.xml.ts'];
|
|
167
|
+
for (const p of paths) {
|
|
168
|
+
if (fs.existsSync(path.join(projectDir, p)))
|
|
169
|
+
return p;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
findLlmsTxt(projectDir) {
|
|
174
|
+
const paths = ['public/llms.txt', 'llms.txt'];
|
|
175
|
+
for (const p of paths) {
|
|
176
|
+
if (fs.existsSync(path.join(projectDir, p)))
|
|
177
|
+
return p;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
extractDynamicParams(routePath) {
|
|
182
|
+
const params = [];
|
|
183
|
+
const matches = routePath.match(/\[\.{0,3}([a-zA-Z0-9_-]+)\]/g);
|
|
184
|
+
if (matches) {
|
|
185
|
+
for (const m of matches) {
|
|
186
|
+
params.push(m.replace(/[\[\].]/g, ''));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return params;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FrameworkAdapter, MetadataLocationInfo, CanonicalLocationInfo, SchemaLocationInfo } from './types.js';
|
|
2
|
+
import { DiscoveredRoute, ProjectFrameworkInfo, RouteSourceMapping } from '../types/findings.js';
|
|
3
|
+
export declare class NextAppAdapter implements FrameworkAdapter {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
detect(projectDir: string): boolean;
|
|
7
|
+
getProjectInfo(projectDir: string): ProjectFrameworkInfo;
|
|
8
|
+
discoverRoutes(projectDir: string): DiscoveredRoute[];
|
|
9
|
+
mapRouteToSource(targetUrl: string, routes: DiscoveredRoute[]): RouteSourceMapping;
|
|
10
|
+
findMetadataImplementation(projectDir: string, route: DiscoveredRoute): Promise<MetadataLocationInfo>;
|
|
11
|
+
findCanonicalDeclaration(projectDir: string, route: DiscoveredRoute): Promise<CanonicalLocationInfo>;
|
|
12
|
+
findSchemaDeclaration(projectDir: string, route: DiscoveredRoute): Promise<SchemaLocationInfo>;
|
|
13
|
+
findRobotsConfig(projectDir: string): string | null;
|
|
14
|
+
findSitemapConfig(projectDir: string): string | null;
|
|
15
|
+
findLlmsTxt(projectDir: string): string | null;
|
|
16
|
+
private extractDynamicParams;
|
|
17
|
+
}
|