@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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/dist/astro-adapter.d.ts +13 -0
  3. package/dist/astro-adapter.d.ts.map +1 -0
  4. package/dist/astro-adapter.js +95 -0
  5. package/dist/astro-adapter.js.map +1 -0
  6. package/dist/contract.d.ts +21 -0
  7. package/dist/contract.d.ts.map +1 -0
  8. package/dist/contract.js +2 -0
  9. package/dist/contract.js.map +1 -0
  10. package/dist/detector.d.ts +15 -0
  11. package/dist/detector.d.ts.map +1 -0
  12. package/dist/detector.js +40 -0
  13. package/dist/detector.js.map +1 -0
  14. package/dist/django-adapter.d.ts +13 -0
  15. package/dist/django-adapter.d.ts.map +1 -0
  16. package/dist/django-adapter.js +133 -0
  17. package/dist/django-adapter.js.map +1 -0
  18. package/dist/index.d.ts +24 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +24 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/nextjs-adapter.d.ts +15 -0
  23. package/dist/nextjs-adapter.d.ts.map +1 -0
  24. package/dist/nextjs-adapter.js +83 -0
  25. package/dist/nextjs-adapter.js.map +1 -0
  26. package/dist/nuxt-adapter.d.ts +13 -0
  27. package/dist/nuxt-adapter.d.ts.map +1 -0
  28. package/dist/nuxt-adapter.js +88 -0
  29. package/dist/nuxt-adapter.js.map +1 -0
  30. package/dist/parsers/astro-parser.d.ts +8 -0
  31. package/dist/parsers/astro-parser.d.ts.map +1 -0
  32. package/dist/parsers/astro-parser.js +218 -0
  33. package/dist/parsers/astro-parser.js.map +1 -0
  34. package/dist/parsers/dtl-parser.d.ts +7 -0
  35. package/dist/parsers/dtl-parser.d.ts.map +1 -0
  36. package/dist/parsers/dtl-parser.js +143 -0
  37. package/dist/parsers/dtl-parser.js.map +1 -0
  38. package/dist/parsers/html-parser.d.ts +3 -0
  39. package/dist/parsers/html-parser.d.ts.map +1 -0
  40. package/dist/parsers/html-parser.js +195 -0
  41. package/dist/parsers/html-parser.js.map +1 -0
  42. package/dist/parsers/layout-resolver.d.ts +44 -0
  43. package/dist/parsers/layout-resolver.d.ts.map +1 -0
  44. package/dist/parsers/layout-resolver.js +85 -0
  45. package/dist/parsers/layout-resolver.js.map +1 -0
  46. package/dist/parsers/php-route-parser.d.ts +11 -0
  47. package/dist/parsers/php-route-parser.d.ts.map +1 -0
  48. package/dist/parsers/php-route-parser.js +194 -0
  49. package/dist/parsers/php-route-parser.js.map +1 -0
  50. package/dist/parsers/php-template-parser.d.ts +3 -0
  51. package/dist/parsers/php-template-parser.d.ts.map +1 -0
  52. package/dist/parsers/php-template-parser.js +230 -0
  53. package/dist/parsers/php-template-parser.js.map +1 -0
  54. package/dist/parsers/python-url-parser.d.ts +19 -0
  55. package/dist/parsers/python-url-parser.d.ts.map +1 -0
  56. package/dist/parsers/python-url-parser.js +69 -0
  57. package/dist/parsers/python-url-parser.js.map +1 -0
  58. package/dist/parsers/remix-parser.d.ts +3 -0
  59. package/dist/parsers/remix-parser.d.ts.map +1 -0
  60. package/dist/parsers/remix-parser.js +139 -0
  61. package/dist/parsers/remix-parser.js.map +1 -0
  62. package/dist/parsers/robots-sitemap-parser.d.ts +15 -0
  63. package/dist/parsers/robots-sitemap-parser.d.ts.map +1 -0
  64. package/dist/parsers/robots-sitemap-parser.js +100 -0
  65. package/dist/parsers/robots-sitemap-parser.js.map +1 -0
  66. package/dist/parsers/route-normalizer.d.ts +11 -0
  67. package/dist/parsers/route-normalizer.d.ts.map +1 -0
  68. package/dist/parsers/route-normalizer.js +59 -0
  69. package/dist/parsers/route-normalizer.js.map +1 -0
  70. package/dist/parsers/svelte-parser.d.ts +3 -0
  71. package/dist/parsers/svelte-parser.d.ts.map +1 -0
  72. package/dist/parsers/svelte-parser.js +148 -0
  73. package/dist/parsers/svelte-parser.js.map +1 -0
  74. package/dist/parsers/tsx-ast-parser.d.ts +4 -0
  75. package/dist/parsers/tsx-ast-parser.d.ts.map +1 -0
  76. package/dist/parsers/tsx-ast-parser.js +380 -0
  77. package/dist/parsers/tsx-ast-parser.js.map +1 -0
  78. package/dist/parsers/vue-sfc-parser.d.ts +3 -0
  79. package/dist/parsers/vue-sfc-parser.d.ts.map +1 -0
  80. package/dist/parsers/vue-sfc-parser.js +183 -0
  81. package/dist/parsers/vue-sfc-parser.js.map +1 -0
  82. package/dist/php-adapter.d.ts +12 -0
  83. package/dist/php-adapter.d.ts.map +1 -0
  84. package/dist/php-adapter.js +42 -0
  85. package/dist/php-adapter.js.map +1 -0
  86. package/dist/remix-adapter.d.ts +13 -0
  87. package/dist/remix-adapter.d.ts.map +1 -0
  88. package/dist/remix-adapter.js +91 -0
  89. package/dist/remix-adapter.js.map +1 -0
  90. package/dist/static-adapter.d.ts +12 -0
  91. package/dist/static-adapter.d.ts.map +1 -0
  92. package/dist/static-adapter.js +44 -0
  93. package/dist/static-adapter.js.map +1 -0
  94. package/dist/sveltekit-adapter.d.ts +13 -0
  95. package/dist/sveltekit-adapter.d.ts.map +1 -0
  96. package/dist/sveltekit-adapter.js +89 -0
  97. package/dist/sveltekit-adapter.js.map +1 -0
  98. package/package.json +23 -3
  99. package/src/astro-adapter.ts +126 -0
  100. package/src/contract.ts +40 -0
  101. package/src/detector.ts +46 -0
  102. package/src/django-adapter.ts +173 -0
  103. package/src/index.ts +23 -0
  104. package/src/nextjs-adapter.ts +116 -0
  105. package/src/nuxt-adapter.ts +118 -0
  106. package/src/parsers/astro-parser.ts +249 -0
  107. package/src/parsers/dtl-parser.ts +166 -0
  108. package/src/parsers/html-parser.ts +214 -0
  109. package/src/parsers/layout-resolver.ts +112 -0
  110. package/src/parsers/php-route-parser.ts +254 -0
  111. package/src/parsers/php-template-parser.ts +303 -0
  112. package/src/parsers/python-url-parser.ts +92 -0
  113. package/src/parsers/remix-parser.ts +161 -0
  114. package/src/parsers/robots-sitemap-parser.ts +128 -0
  115. package/src/parsers/route-normalizer.ts +70 -0
  116. package/src/parsers/svelte-parser.ts +167 -0
  117. package/src/parsers/tsx-ast-parser.ts +404 -0
  118. package/src/parsers/vue-sfc-parser.ts +207 -0
  119. package/src/php-adapter.ts +68 -0
  120. package/src/remix-adapter.ts +123 -0
  121. package/src/static-adapter.ts +62 -0
  122. package/src/sveltekit-adapter.ts +119 -0
  123. package/tsconfig.json +9 -0
  124. 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
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }
9
+
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.