@seo-partner/adapters 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/astro-adapter.d.ts +13 -0
- package/dist/astro-adapter.d.ts.map +1 -0
- package/dist/astro-adapter.js +95 -0
- package/dist/astro-adapter.js.map +1 -0
- package/dist/contract.d.ts +21 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/detector.d.ts +15 -0
- package/dist/detector.d.ts.map +1 -0
- package/dist/detector.js +40 -0
- package/dist/detector.js.map +1 -0
- package/dist/django-adapter.d.ts +13 -0
- package/dist/django-adapter.d.ts.map +1 -0
- package/dist/django-adapter.js +133 -0
- package/dist/django-adapter.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs-adapter.d.ts +15 -0
- package/dist/nextjs-adapter.d.ts.map +1 -0
- package/dist/nextjs-adapter.js +83 -0
- package/dist/nextjs-adapter.js.map +1 -0
- package/dist/nuxt-adapter.d.ts +13 -0
- package/dist/nuxt-adapter.d.ts.map +1 -0
- package/dist/nuxt-adapter.js +88 -0
- package/dist/nuxt-adapter.js.map +1 -0
- package/dist/parsers/astro-parser.d.ts +8 -0
- package/dist/parsers/astro-parser.d.ts.map +1 -0
- package/dist/parsers/astro-parser.js +218 -0
- package/dist/parsers/astro-parser.js.map +1 -0
- package/dist/parsers/dtl-parser.d.ts +7 -0
- package/dist/parsers/dtl-parser.d.ts.map +1 -0
- package/dist/parsers/dtl-parser.js +143 -0
- package/dist/parsers/dtl-parser.js.map +1 -0
- package/dist/parsers/html-parser.d.ts +3 -0
- package/dist/parsers/html-parser.d.ts.map +1 -0
- package/dist/parsers/html-parser.js +195 -0
- package/dist/parsers/html-parser.js.map +1 -0
- package/dist/parsers/layout-resolver.d.ts +44 -0
- package/dist/parsers/layout-resolver.d.ts.map +1 -0
- package/dist/parsers/layout-resolver.js +85 -0
- package/dist/parsers/layout-resolver.js.map +1 -0
- package/dist/parsers/php-route-parser.d.ts +11 -0
- package/dist/parsers/php-route-parser.d.ts.map +1 -0
- package/dist/parsers/php-route-parser.js +194 -0
- package/dist/parsers/php-route-parser.js.map +1 -0
- package/dist/parsers/php-template-parser.d.ts +3 -0
- package/dist/parsers/php-template-parser.d.ts.map +1 -0
- package/dist/parsers/php-template-parser.js +230 -0
- package/dist/parsers/php-template-parser.js.map +1 -0
- package/dist/parsers/python-url-parser.d.ts +19 -0
- package/dist/parsers/python-url-parser.d.ts.map +1 -0
- package/dist/parsers/python-url-parser.js +69 -0
- package/dist/parsers/python-url-parser.js.map +1 -0
- package/dist/parsers/remix-parser.d.ts +3 -0
- package/dist/parsers/remix-parser.d.ts.map +1 -0
- package/dist/parsers/remix-parser.js +139 -0
- package/dist/parsers/remix-parser.js.map +1 -0
- package/dist/parsers/robots-sitemap-parser.d.ts +15 -0
- package/dist/parsers/robots-sitemap-parser.d.ts.map +1 -0
- package/dist/parsers/robots-sitemap-parser.js +100 -0
- package/dist/parsers/robots-sitemap-parser.js.map +1 -0
- package/dist/parsers/route-normalizer.d.ts +11 -0
- package/dist/parsers/route-normalizer.d.ts.map +1 -0
- package/dist/parsers/route-normalizer.js +59 -0
- package/dist/parsers/route-normalizer.js.map +1 -0
- package/dist/parsers/svelte-parser.d.ts +3 -0
- package/dist/parsers/svelte-parser.d.ts.map +1 -0
- package/dist/parsers/svelte-parser.js +148 -0
- package/dist/parsers/svelte-parser.js.map +1 -0
- package/dist/parsers/tsx-ast-parser.d.ts +4 -0
- package/dist/parsers/tsx-ast-parser.d.ts.map +1 -0
- package/dist/parsers/tsx-ast-parser.js +380 -0
- package/dist/parsers/tsx-ast-parser.js.map +1 -0
- package/dist/parsers/vue-sfc-parser.d.ts +3 -0
- package/dist/parsers/vue-sfc-parser.d.ts.map +1 -0
- package/dist/parsers/vue-sfc-parser.js +183 -0
- package/dist/parsers/vue-sfc-parser.js.map +1 -0
- package/dist/php-adapter.d.ts +12 -0
- package/dist/php-adapter.d.ts.map +1 -0
- package/dist/php-adapter.js +42 -0
- package/dist/php-adapter.js.map +1 -0
- package/dist/remix-adapter.d.ts +13 -0
- package/dist/remix-adapter.d.ts.map +1 -0
- package/dist/remix-adapter.js +91 -0
- package/dist/remix-adapter.js.map +1 -0
- package/dist/static-adapter.d.ts +12 -0
- package/dist/static-adapter.d.ts.map +1 -0
- package/dist/static-adapter.js +44 -0
- package/dist/static-adapter.js.map +1 -0
- package/dist/sveltekit-adapter.d.ts +13 -0
- package/dist/sveltekit-adapter.d.ts.map +1 -0
- package/dist/sveltekit-adapter.js +89 -0
- package/dist/sveltekit-adapter.js.map +1 -0
- package/package.json +23 -3
- package/src/astro-adapter.ts +126 -0
- package/src/contract.ts +40 -0
- package/src/detector.ts +46 -0
- package/src/django-adapter.ts +173 -0
- package/src/index.ts +23 -0
- package/src/nextjs-adapter.ts +116 -0
- package/src/nuxt-adapter.ts +118 -0
- package/src/parsers/astro-parser.ts +249 -0
- package/src/parsers/dtl-parser.ts +166 -0
- package/src/parsers/html-parser.ts +214 -0
- package/src/parsers/layout-resolver.ts +112 -0
- package/src/parsers/php-route-parser.ts +254 -0
- package/src/parsers/php-template-parser.ts +303 -0
- package/src/parsers/python-url-parser.ts +92 -0
- package/src/parsers/remix-parser.ts +161 -0
- package/src/parsers/robots-sitemap-parser.ts +128 -0
- package/src/parsers/route-normalizer.ts +70 -0
- package/src/parsers/svelte-parser.ts +167 -0
- package/src/parsers/tsx-ast-parser.ts +404 -0
- package/src/parsers/vue-sfc-parser.ts +207 -0
- package/src/php-adapter.ts +68 -0
- package/src/remix-adapter.ts +123 -0
- package/src/static-adapter.ts +62 -0
- package/src/sveltekit-adapter.ts +119 -0
- package/tsconfig.json +9 -0
- package/README.md +0 -3
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { DiscoveredRoute, ExtractedRouteData } from '@seo-partner/core';
|
|
2
|
+
import type { AdapterCapabilities, AdapterVersion, FrameworkAdapter } from './contract.js';
|
|
3
|
+
import { parseRemixRoute } from './parsers/remix-parser.js';
|
|
4
|
+
import { normalizeRoutePattern } from './parsers/route-normalizer.js';
|
|
5
|
+
import { LayoutResolver } from './parsers/layout-resolver.js';
|
|
6
|
+
|
|
7
|
+
export class RemixAdapter implements FrameworkAdapter {
|
|
8
|
+
public readonly id = 'remix';
|
|
9
|
+
public readonly name = 'Remix / React Router v7';
|
|
10
|
+
public readonly version: AdapterVersion = '1.0';
|
|
11
|
+
public readonly capabilities: AdapterCapabilities = {
|
|
12
|
+
routes: true,
|
|
13
|
+
metadata: true,
|
|
14
|
+
headings: true,
|
|
15
|
+
internalLinks: true,
|
|
16
|
+
jsonLd: true,
|
|
17
|
+
images: true,
|
|
18
|
+
robotsAndSitemap: true,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
private readonly layoutResolver = new LayoutResolver();
|
|
22
|
+
|
|
23
|
+
public async detect(
|
|
24
|
+
projectRoot: string,
|
|
25
|
+
filePaths: ReadonlyArray<string>,
|
|
26
|
+
contentsByPath: ReadonlyMap<string, string>,
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
for (const filePath of filePaths) {
|
|
29
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
30
|
+
if (
|
|
31
|
+
normalized === 'remix.config.js' ||
|
|
32
|
+
normalized === 'remix.config.ts' ||
|
|
33
|
+
normalized === 'remix.config.mjs' ||
|
|
34
|
+
normalized === 'react-router.config.ts' ||
|
|
35
|
+
normalized === 'react-router.config.js'
|
|
36
|
+
) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (normalized === 'package.json') {
|
|
40
|
+
const content = contentsByPath.get(filePath) ?? '';
|
|
41
|
+
if (
|
|
42
|
+
content.includes('"@remix-run/') ||
|
|
43
|
+
content.includes('"react-router"')
|
|
44
|
+
) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async discoverRoutes(
|
|
53
|
+
projectRoot: string,
|
|
54
|
+
filePaths: ReadonlyArray<string>,
|
|
55
|
+
contentsByPath: ReadonlyMap<string, string>,
|
|
56
|
+
sitemapPaths: ReadonlySet<string>,
|
|
57
|
+
): Promise<ReadonlyArray<DiscoveredRoute>> {
|
|
58
|
+
const routes: DiscoveredRoute[] = [];
|
|
59
|
+
|
|
60
|
+
// Pre-cache root layout from app/root.tsx
|
|
61
|
+
for (const filePath of filePaths) {
|
|
62
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
63
|
+
if (normalized === 'app/root.tsx' || normalized === 'app/root.jsx') {
|
|
64
|
+
const content = contentsByPath.get(filePath) ?? '';
|
|
65
|
+
const parsed = parseRemixRoute(
|
|
66
|
+
{
|
|
67
|
+
pattern: '',
|
|
68
|
+
filePath,
|
|
69
|
+
routerKind: 'remix',
|
|
70
|
+
renderingMode: 'ssr-ssg',
|
|
71
|
+
isPrivate: false,
|
|
72
|
+
inSitemap: false,
|
|
73
|
+
},
|
|
74
|
+
content,
|
|
75
|
+
);
|
|
76
|
+
this.layoutResolver.registerLayout({
|
|
77
|
+
filePath,
|
|
78
|
+
htmlLang: parsed.htmlLang,
|
|
79
|
+
title: parsed.title,
|
|
80
|
+
description: parsed.description,
|
|
81
|
+
canonical: parsed.canonical,
|
|
82
|
+
robots: parsed.robots,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const filePath of filePaths) {
|
|
88
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
89
|
+
if (!normalized.startsWith('app/routes/')) continue;
|
|
90
|
+
if (!/\.(tsx|jsx|ts|js)$/i.test(normalized)) continue;
|
|
91
|
+
|
|
92
|
+
const relativeInsideRoutes = normalized.slice('app/routes/'.length);
|
|
93
|
+
const pattern = normalizeRoutePattern(relativeInsideRoutes, 'remix');
|
|
94
|
+
const inSitemap = sitemapPaths.has(pattern);
|
|
95
|
+
|
|
96
|
+
routes.push({
|
|
97
|
+
pattern,
|
|
98
|
+
filePath,
|
|
99
|
+
routerKind: 'remix',
|
|
100
|
+
renderingMode: 'ssr-ssg',
|
|
101
|
+
isPrivate: false,
|
|
102
|
+
inSitemap,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return routes;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public async extractRoute(
|
|
110
|
+
projectRoot: string,
|
|
111
|
+
route: DiscoveredRoute,
|
|
112
|
+
fileContent: string,
|
|
113
|
+
): Promise<ExtractedRouteData> {
|
|
114
|
+
let extracted = parseRemixRoute(route, fileContent);
|
|
115
|
+
|
|
116
|
+
// Apply root layout inheritance
|
|
117
|
+
const layoutCandidates = ['app/root.tsx', 'app/root.jsx'];
|
|
118
|
+
extracted = this.layoutResolver.applyInheritance(extracted, layoutCandidates);
|
|
119
|
+
|
|
120
|
+
return extracted;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { DiscoveredRoute, ExtractedRouteData } from '@seo-partner/core';
|
|
2
|
+
import type { AdapterCapabilities, FrameworkAdapter } from './contract.js';
|
|
3
|
+
import { isPrivateRoutePattern } from './nextjs-adapter.js';
|
|
4
|
+
import { parseStaticHtmlOrMarkdown } from './parsers/html-parser.js';
|
|
5
|
+
|
|
6
|
+
export class StaticAdapter implements FrameworkAdapter {
|
|
7
|
+
public readonly id = 'static-html';
|
|
8
|
+
public readonly name = 'Static HTML & Markdown';
|
|
9
|
+
public readonly version = '1.0' as const;
|
|
10
|
+
public readonly capabilities: AdapterCapabilities = {
|
|
11
|
+
routes: true,
|
|
12
|
+
metadata: true,
|
|
13
|
+
headings: true,
|
|
14
|
+
internalLinks: true,
|
|
15
|
+
jsonLd: true,
|
|
16
|
+
images: true,
|
|
17
|
+
robotsAndSitemap: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
public async detect(
|
|
21
|
+
_projectRoot: string,
|
|
22
|
+
filePaths: ReadonlyArray<string>,
|
|
23
|
+
): Promise<boolean> {
|
|
24
|
+
return filePaths.some((p) => p.endsWith('.html'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async discoverRoutes(
|
|
28
|
+
_projectRoot: string,
|
|
29
|
+
filePaths: ReadonlyArray<string>,
|
|
30
|
+
_contentsByPath: ReadonlyMap<string, string>,
|
|
31
|
+
sitemapPaths: ReadonlySet<string>,
|
|
32
|
+
): Promise<ReadonlyArray<DiscoveredRoute>> {
|
|
33
|
+
const routes: DiscoveredRoute[] = [];
|
|
34
|
+
for (const filePath of filePaths) {
|
|
35
|
+
if (filePath.endsWith('.html')) {
|
|
36
|
+
const clean = filePath
|
|
37
|
+
.replace(/^public\//, '')
|
|
38
|
+
.replace(/index\.html$/, '')
|
|
39
|
+
.replace(/\.html$/, '');
|
|
40
|
+
const pattern = clean.startsWith('/') ? clean : `/${clean}`;
|
|
41
|
+
routes.push({
|
|
42
|
+
pattern: pattern === '' ? '/' : pattern,
|
|
43
|
+
filePath,
|
|
44
|
+
routerKind: 'static-html',
|
|
45
|
+
renderingMode: 'static',
|
|
46
|
+
isPrivate: isPrivateRoutePattern(pattern),
|
|
47
|
+
inSitemap: sitemapPaths.has(pattern),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return routes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async extractRoute(
|
|
55
|
+
_projectRoot: string,
|
|
56
|
+
route: DiscoveredRoute,
|
|
57
|
+
fileContent: string,
|
|
58
|
+
): Promise<ExtractedRouteData> {
|
|
59
|
+
return parseStaticHtmlOrMarkdown(route, fileContent);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { DiscoveredRoute, ExtractedRouteData } from '@seo-partner/core';
|
|
2
|
+
import type { AdapterCapabilities, AdapterVersion, FrameworkAdapter } from './contract.js';
|
|
3
|
+
import { parseSvelteComponent } from './parsers/svelte-parser.js';
|
|
4
|
+
import { normalizeRoutePattern } from './parsers/route-normalizer.js';
|
|
5
|
+
import { LayoutResolver } from './parsers/layout-resolver.js';
|
|
6
|
+
|
|
7
|
+
export class SvelteKitAdapter implements FrameworkAdapter {
|
|
8
|
+
public readonly id = 'sveltekit';
|
|
9
|
+
public readonly name = 'SvelteKit';
|
|
10
|
+
public readonly version: AdapterVersion = '1.0';
|
|
11
|
+
public readonly capabilities: AdapterCapabilities = {
|
|
12
|
+
routes: true,
|
|
13
|
+
metadata: true,
|
|
14
|
+
headings: true,
|
|
15
|
+
internalLinks: true,
|
|
16
|
+
jsonLd: true,
|
|
17
|
+
images: true,
|
|
18
|
+
robotsAndSitemap: true,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
private readonly layoutResolver = new LayoutResolver();
|
|
22
|
+
|
|
23
|
+
public async detect(
|
|
24
|
+
projectRoot: string,
|
|
25
|
+
filePaths: ReadonlyArray<string>,
|
|
26
|
+
contentsByPath: ReadonlyMap<string, string>,
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
for (const filePath of filePaths) {
|
|
29
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
30
|
+
if (
|
|
31
|
+
normalized === 'svelte.config.js' ||
|
|
32
|
+
normalized === 'svelte.config.ts' ||
|
|
33
|
+
normalized === 'svelte.config.mjs' ||
|
|
34
|
+
normalized === 'svelte.config.cjs'
|
|
35
|
+
) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (normalized === 'package.json') {
|
|
39
|
+
const content = contentsByPath.get(filePath) ?? '';
|
|
40
|
+
if (content.includes('"@sveltejs/kit"')) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async discoverRoutes(
|
|
49
|
+
projectRoot: string,
|
|
50
|
+
filePaths: ReadonlyArray<string>,
|
|
51
|
+
contentsByPath: ReadonlyMap<string, string>,
|
|
52
|
+
sitemapPaths: ReadonlySet<string>,
|
|
53
|
+
): Promise<ReadonlyArray<DiscoveredRoute>> {
|
|
54
|
+
const routes: DiscoveredRoute[] = [];
|
|
55
|
+
|
|
56
|
+
// Pre-cache layouts from src/routes/**/+layout.svelte
|
|
57
|
+
for (const filePath of filePaths) {
|
|
58
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
59
|
+
if (normalized.startsWith('src/routes/') && normalized.endsWith('+layout.svelte')) {
|
|
60
|
+
const content = contentsByPath.get(filePath) ?? '';
|
|
61
|
+
const parsed = parseSvelteComponent(
|
|
62
|
+
{
|
|
63
|
+
pattern: '',
|
|
64
|
+
filePath,
|
|
65
|
+
routerKind: 'sveltekit',
|
|
66
|
+
renderingMode: 'ssr-ssg',
|
|
67
|
+
isPrivate: false,
|
|
68
|
+
inSitemap: false,
|
|
69
|
+
},
|
|
70
|
+
content,
|
|
71
|
+
);
|
|
72
|
+
this.layoutResolver.registerLayout({
|
|
73
|
+
filePath,
|
|
74
|
+
htmlLang: parsed.htmlLang,
|
|
75
|
+
title: parsed.title,
|
|
76
|
+
description: parsed.description,
|
|
77
|
+
canonical: parsed.canonical,
|
|
78
|
+
robots: parsed.robots,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const filePath of filePaths) {
|
|
84
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
85
|
+
if (!normalized.startsWith('src/routes/')) continue;
|
|
86
|
+
if (!normalized.endsWith('+page.svelte')) continue;
|
|
87
|
+
|
|
88
|
+
const relativeInsideRoutes = normalized.slice('src/routes/'.length);
|
|
89
|
+
const pattern = normalizeRoutePattern(relativeInsideRoutes, 'sveltekit');
|
|
90
|
+
const inSitemap = sitemapPaths.has(pattern);
|
|
91
|
+
|
|
92
|
+
routes.push({
|
|
93
|
+
pattern,
|
|
94
|
+
filePath,
|
|
95
|
+
routerKind: 'sveltekit',
|
|
96
|
+
renderingMode: 'ssr-ssg',
|
|
97
|
+
isPrivate: false,
|
|
98
|
+
inSitemap,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return routes;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public async extractRoute(
|
|
106
|
+
projectRoot: string,
|
|
107
|
+
route: DiscoveredRoute,
|
|
108
|
+
fileContent: string,
|
|
109
|
+
): Promise<ExtractedRouteData> {
|
|
110
|
+
let extracted = parseSvelteComponent(route, fileContent);
|
|
111
|
+
|
|
112
|
+
// Apply hierarchical layout inheritance from leaf up to root
|
|
113
|
+
const parentLayouts = this.layoutResolver.findSvelteKitLayoutPaths(route.filePath);
|
|
114
|
+
extracted = this.layoutResolver.applyInheritance(extracted, parentLayouts);
|
|
115
|
+
|
|
116
|
+
return extracted;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
package/tsconfig.json
ADDED
package/README.md
DELETED