@seo-partner/adapters 1.0.0 → 1.0.3

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 (61) hide show
  1. package/dist/ast-merger.d.ts +24 -0
  2. package/dist/ast-merger.d.ts.map +1 -0
  3. package/dist/ast-merger.js +85 -0
  4. package/dist/ast-merger.js.map +1 -0
  5. package/dist/astro-adapter.d.ts +6 -0
  6. package/dist/astro-adapter.d.ts.map +1 -1
  7. package/dist/astro-adapter.js +19 -0
  8. package/dist/astro-adapter.js.map +1 -1
  9. package/dist/blog-scaffold-generator.d.ts +8 -0
  10. package/dist/blog-scaffold-generator.d.ts.map +1 -0
  11. package/dist/blog-scaffold-generator.js +138 -0
  12. package/dist/blog-scaffold-generator.js.map +1 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +8 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/laravel-adapter.d.ts +7 -0
  18. package/dist/laravel-adapter.d.ts.map +1 -0
  19. package/dist/laravel-adapter.js +35 -0
  20. package/dist/laravel-adapter.js.map +1 -0
  21. package/dist/layout-title-resolver.d.ts +8 -0
  22. package/dist/layout-title-resolver.d.ts.map +1 -0
  23. package/dist/layout-title-resolver.js +28 -0
  24. package/dist/layout-title-resolver.js.map +1 -0
  25. package/dist/nuxt-adapter.d.ts +6 -0
  26. package/dist/nuxt-adapter.d.ts.map +1 -1
  27. package/dist/nuxt-adapter.js +20 -0
  28. package/dist/nuxt-adapter.js.map +1 -1
  29. package/dist/php-native-adapter.d.ts +7 -0
  30. package/dist/php-native-adapter.d.ts.map +1 -0
  31. package/dist/php-native-adapter.js +31 -0
  32. package/dist/php-native-adapter.js.map +1 -0
  33. package/dist/sitemap-builder.d.ts +10 -0
  34. package/dist/sitemap-builder.d.ts.map +1 -0
  35. package/dist/sitemap-builder.js +37 -0
  36. package/dist/sitemap-builder.js.map +1 -0
  37. package/dist/skeleton-detector.d.ts +14 -0
  38. package/dist/skeleton-detector.d.ts.map +1 -0
  39. package/dist/skeleton-detector.js +33 -0
  40. package/dist/skeleton-detector.js.map +1 -0
  41. package/dist/sveltekit-adapter.d.ts +6 -0
  42. package/dist/sveltekit-adapter.d.ts.map +1 -1
  43. package/dist/sveltekit-adapter.js +26 -0
  44. package/dist/sveltekit-adapter.js.map +1 -1
  45. package/dist/symfony-adapter.d.ts +7 -0
  46. package/dist/symfony-adapter.d.ts.map +1 -0
  47. package/dist/symfony-adapter.js +23 -0
  48. package/dist/symfony-adapter.js.map +1 -0
  49. package/package.json +2 -2
  50. package/src/ast-merger.ts +123 -0
  51. package/src/astro-adapter.ts +155 -126
  52. package/src/blog-scaffold-generator.ts +153 -0
  53. package/src/index.ts +8 -0
  54. package/src/laravel-adapter.ts +45 -0
  55. package/src/layout-title-resolver.ts +37 -0
  56. package/src/nuxt-adapter.ts +148 -118
  57. package/src/php-native-adapter.ts +46 -0
  58. package/src/sitemap-builder.ts +57 -0
  59. package/src/skeleton-detector.ts +56 -0
  60. package/src/sveltekit-adapter.ts +160 -119
  61. package/src/symfony-adapter.ts +33 -0
@@ -0,0 +1,37 @@
1
+ export function buildSitemapXml(routes, siteUrl) {
2
+ const cleanBase = siteUrl.replace(/\/+$/, '');
3
+ const publicRoutes = routes.filter((r) => !r.isPrivate);
4
+ const urlEntries = publicRoutes.map((r) => {
5
+ const cleanPattern = r.pattern.startsWith('/') ? r.pattern : `/${r.pattern}`;
6
+ const loc = `${cleanBase}${cleanPattern === '/' ? '' : cleanPattern}`;
7
+ const lastmodTag = r.lastmod ? `\n <lastmod>${r.lastmod}</lastmod>` : '';
8
+ const changefreqTag = r.changefreq ? `\n <changefreq>${r.changefreq}</changefreq>` : '';
9
+ const priorityTag = typeof r.priority === 'number' ? `\n <priority>${r.priority.toFixed(1)}</priority>` : '';
10
+ return ` <url>\n <loc>${loc}</loc>${lastmodTag}${changefreqTag}${priorityTag}\n </url>`;
11
+ });
12
+ return [
13
+ '<?xml version="1.0" encoding="UTF-8"?>',
14
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
15
+ ...urlEntries,
16
+ '</urlset>',
17
+ ].join('\n');
18
+ }
19
+ export function buildNextJsAppSitemap(routes, siteUrl) {
20
+ const cleanBase = siteUrl.replace(/\/+$/, '');
21
+ const publicRoutes = routes.filter((r) => !r.isPrivate);
22
+ const items = publicRoutes.map((r) => {
23
+ const cleanPattern = r.pattern.startsWith('/') ? r.pattern : `/${r.pattern}`;
24
+ const loc = `${cleanBase}${cleanPattern === '/' ? '' : cleanPattern}`;
25
+ return ` {\n url: '${loc}',\n lastModified: new Date(),\n changeFrequency: 'weekly',\n priority: ${cleanPattern === '/' ? '1.0' : '0.8'},\n },`;
26
+ });
27
+ return [
28
+ "import type { MetadataRoute } from 'next';",
29
+ '',
30
+ 'export default function sitemap(): MetadataRoute.Sitemap {',
31
+ ' return [',
32
+ ...items,
33
+ ' ];',
34
+ '}',
35
+ ].join('\n');
36
+ }
37
+ //# sourceMappingURL=sitemap-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-builder.js","sourceRoot":"","sources":["../src/sitemap-builder.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,eAAe,CAC7B,MAAwC,EACxC,OAAe;IAEf,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACxC,MAAM,YAAY,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7E,MAAM,GAAG,GAAG,GAAG,SAAS,GAAG,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;QACtE,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,MAAM,aAAa,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,UAAU,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QAEhH,OAAO,qBAAqB,GAAG,SAAS,UAAU,GAAG,aAAa,GAAG,WAAW,YAAY,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,wCAAwC;QACxC,8DAA8D;QAC9D,GAAG,UAAU;QACb,WAAW;KACZ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAwC,EACxC,OAAe;IAEf,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACnC,MAAM,YAAY,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7E,MAAM,GAAG,GAAG,GAAG,SAAS,GAAG,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;QACtE,OAAO,sBAAsB,GAAG,0FAA0F,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC;IAC5K,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,4CAA4C;QAC5C,EAAE;QACF,4DAA4D;QAC5D,YAAY;QACZ,GAAG,KAAK;QACR,MAAM;QACN,GAAG;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface SeoFileInventory {
2
+ robots: {
3
+ appRobotsTs?: string;
4
+ publicRobotsTxt?: string;
5
+ hasConflict: boolean;
6
+ };
7
+ sitemap: {
8
+ appSitemapTs?: string;
9
+ publicSitemapXml?: string;
10
+ hasConflict: boolean;
11
+ };
12
+ }
13
+ export declare function detectSeoFileInventory(projectRoot: string): SeoFileInventory;
14
+ //# sourceMappingURL=skeleton-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeleton-detector.d.ts","sourceRoot":"","sources":["../src/skeleton-detector.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,OAAO,EAAE;QACP,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAsC5E"}
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function detectSeoFileInventory(projectRoot) {
4
+ const checkExists = (rel) => {
5
+ const full = path.join(projectRoot, rel);
6
+ return fs.existsSync(full) ? rel : undefined;
7
+ };
8
+ const appRobotsTs = checkExists('app/robots.ts') ||
9
+ checkExists('app/robots.js') ||
10
+ checkExists('src/app/robots.ts') ||
11
+ checkExists('src/app/robots.js');
12
+ const publicRobotsTxt = checkExists('public/robots.txt') ||
13
+ checkExists('robots.txt');
14
+ const appSitemapTs = checkExists('app/sitemap.ts') ||
15
+ checkExists('app/sitemap.js') ||
16
+ checkExists('src/app/sitemap.ts') ||
17
+ checkExists('src/app/sitemap.js');
18
+ const publicSitemapXml = checkExists('public/sitemap.xml') ||
19
+ checkExists('sitemap.xml');
20
+ return {
21
+ robots: {
22
+ appRobotsTs,
23
+ publicRobotsTxt,
24
+ hasConflict: Boolean(appRobotsTs && publicRobotsTxt),
25
+ },
26
+ sitemap: {
27
+ appSitemapTs,
28
+ publicSitemapXml,
29
+ hasConflict: Boolean(appSitemapTs && publicSitemapXml),
30
+ },
31
+ };
32
+ }
33
+ //# sourceMappingURL=skeleton-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeleton-detector.js","sourceRoot":"","sources":["../src/skeleton-detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAe7B,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC,CAAC;IAEF,MAAM,WAAW,GACf,WAAW,CAAC,eAAe,CAAC;QAC5B,WAAW,CAAC,eAAe,CAAC;QAC5B,WAAW,CAAC,mBAAmB,CAAC;QAChC,WAAW,CAAC,mBAAmB,CAAC,CAAC;IAEnC,MAAM,eAAe,GACnB,WAAW,CAAC,mBAAmB,CAAC;QAChC,WAAW,CAAC,YAAY,CAAC,CAAC;IAE5B,MAAM,YAAY,GAChB,WAAW,CAAC,gBAAgB,CAAC;QAC7B,WAAW,CAAC,gBAAgB,CAAC;QAC7B,WAAW,CAAC,oBAAoB,CAAC;QACjC,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAEpC,MAAM,gBAAgB,GACpB,WAAW,CAAC,oBAAoB,CAAC;QACjC,WAAW,CAAC,aAAa,CAAC,CAAC;IAE7B,OAAO;QACL,MAAM,EAAE;YACN,WAAW;YACX,eAAe;YACf,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,eAAe,CAAC;SACrD;QACD,OAAO,EAAE;YACP,YAAY;YACZ,gBAAgB;YAChB,WAAW,EAAE,OAAO,CAAC,YAAY,IAAI,gBAAgB,CAAC;SACvD;KACF,CAAC;AACJ,CAAC"}
@@ -10,4 +10,10 @@ export declare class SvelteKitAdapter implements FrameworkAdapter {
10
10
  discoverRoutes(projectRoot: string, filePaths: ReadonlyArray<string>, contentsByPath: ReadonlyMap<string, string>, sitemapPaths: ReadonlySet<string>): Promise<ReadonlyArray<DiscoveredRoute>>;
11
11
  extractRoute(projectRoot: string, route: DiscoveredRoute, fileContent: string): Promise<ExtractedRouteData>;
12
12
  }
13
+ export interface SvelteKitMetaFields {
14
+ title?: string;
15
+ description?: string;
16
+ canonical?: string;
17
+ }
18
+ export declare function patchSvelteKitTemplate(sourceCode: string, meta: SvelteKitMetaFields): string;
13
19
  //# sourceMappingURL=sveltekit-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit-adapter.d.ts","sourceRoot":"","sources":["../src/sveltekit-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAK3F,qBAAa,gBAAiB,YAAW,gBAAgB;IACvD,SAAgB,EAAE,eAAe;IACjC,SAAgB,IAAI,eAAe;IACnC,SAAgB,OAAO,EAAE,cAAc,CAAS;IAChD,SAAgB,YAAY,EAAE,mBAAmB,CAQ/C;IAEF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IAE1C,MAAM,CACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1C,OAAO,CAAC,OAAO,CAAC;IAqBN,cAAc,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3C,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;IAoD7B,YAAY,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC;CAS/B"}
1
+ {"version":3,"file":"sveltekit-adapter.d.ts","sourceRoot":"","sources":["../src/sveltekit-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAK3F,qBAAa,gBAAiB,YAAW,gBAAgB;IACvD,SAAgB,EAAE,eAAe;IACjC,SAAgB,IAAI,eAAe;IACnC,SAAgB,OAAO,EAAE,cAAc,CAAS;IAChD,SAAgB,YAAY,EAAE,mBAAmB,CAQ/C;IAEF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IAE1C,MAAM,CACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1C,OAAO,CAAC,OAAO,CAAC;IAqBN,cAAc,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3C,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;IAoD7B,YAAY,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC;CAS/B;AAGD,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,mBAAmB,GACxB,MAAM,CA8BR"}
@@ -86,4 +86,30 @@ export class SvelteKitAdapter {
86
86
  return extracted;
87
87
  }
88
88
  }
89
+ export function patchSvelteKitTemplate(sourceCode, meta) {
90
+ let result = sourceCode;
91
+ if (/<svelte:head>/i.test(result)) {
92
+ if (meta.title && !/<title>/i.test(result)) {
93
+ result = result.replace(/(<svelte:head>\s*\n)/i, `$1 <title>${meta.title}</title>\n`);
94
+ }
95
+ if (meta.description && !/<meta\s+name=["']description["']/i.test(result)) {
96
+ result = result.replace(/(<svelte:head>\s*\n)/i, `$1 <meta name="description" content="${meta.description.replace(/"/g, '&quot;')}" />\n`);
97
+ }
98
+ }
99
+ else {
100
+ const headLines = ['<svelte:head>'];
101
+ if (meta.title) {
102
+ headLines.push(` <title>${meta.title}</title>`);
103
+ }
104
+ if (meta.description) {
105
+ headLines.push(` <meta name="description" content="${meta.description.replace(/"/g, '&quot;')}" />`);
106
+ }
107
+ if (meta.canonical) {
108
+ headLines.push(` <link rel="canonical" href="${meta.canonical}" />`);
109
+ }
110
+ headLines.push('</svelte:head>\n\n');
111
+ result = headLines.join('\n') + result;
112
+ }
113
+ return result;
114
+ }
89
115
  //# sourceMappingURL=sveltekit-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit-adapter.js","sourceRoot":"","sources":["../src/sveltekit-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sveltekit-adapter.js","sourceRoot":"","sources":["../src/sveltekit-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export interface SymfonyMetaFields {
2
+ title?: string;
3
+ description?: string;
4
+ canonical?: string;
5
+ }
6
+ export declare function patchSymfonyTwigTemplate(sourceCode: string, meta: SymfonyMetaFields): string;
7
+ //# sourceMappingURL=symfony-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symfony-adapter.d.ts","sourceRoot":"","sources":["../src/symfony-adapter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,iBAAiB,GACtB,MAAM,CAsBR"}
@@ -0,0 +1,23 @@
1
+ export function patchSymfonyTwigTemplate(sourceCode, meta) {
2
+ let result = sourceCode;
3
+ if (meta.title && !/{%\s*block\s+title\s*%}/i.test(result)) {
4
+ const titleBlock = `{% block title %}${meta.title}{% endblock %}\n`;
5
+ if (/{%\s*extends[^%]+%}/i.test(result)) {
6
+ result = result.replace(/({%\s*extends[^%]+%}\s*\n)/i, `$1${titleBlock}`);
7
+ }
8
+ else {
9
+ result = titleBlock + result;
10
+ }
11
+ }
12
+ if (meta.description && !/<meta\s+name=["']description["']/i.test(result)) {
13
+ const descTag = `<meta name="description" content="${meta.description.replace(/"/g, '&quot;')}">\n`;
14
+ if (/{%\s*block\s+title\s*%}[\s\S]*?{%\s*endblock\s*%}/i.test(result)) {
15
+ result = result.replace(/({%\s*block\s+title\s*%}[\s\S]*?{%\s*endblock\s*%}\s*\n)/i, `$1${descTag}`);
16
+ }
17
+ else {
18
+ result = descTag + result;
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+ //# sourceMappingURL=symfony-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symfony-adapter.js","sourceRoot":"","sources":["../src/symfony-adapter.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,wBAAwB,CACtC,UAAkB,EAClB,IAAuB;IAEvB,IAAI,MAAM,GAAG,UAAU,CAAC;IAExB,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3D,MAAM,UAAU,GAAG,oBAAoB,IAAI,CAAC,KAAK,kBAAkB,CAAC;QACpE,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,6BAA6B,EAAE,KAAK,UAAU,EAAE,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,mCAAmC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,MAAM,OAAO,GAAG,qCAAqC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC;QACpG,IAAI,oDAAoD,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACtE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,2DAA2D,EAAE,KAAK,OAAO,EAAE,CAAC,CAAC;QACvG,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seo-partner/adapters",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "typescript": "^5.7.2",
15
- "@seo-partner/core": "1.0.0"
15
+ "@seo-partner/core": "1.0.3"
16
16
  },
17
17
  "publishConfig": {
18
18
  "access": "public"
@@ -0,0 +1,123 @@
1
+ export interface MetadataFields {
2
+ title?: string;
3
+ description?: string;
4
+ canonical?: string;
5
+ openGraph?: {
6
+ title?: string;
7
+ description?: string;
8
+ url?: string;
9
+ siteName?: string;
10
+ };
11
+ }
12
+
13
+ export interface AstMergeResult {
14
+ mergedCode: string;
15
+ modified: boolean;
16
+ hasComplexDynamicLogic: boolean;
17
+ isClientComponent: boolean;
18
+ changes: Array<{
19
+ field: string;
20
+ before?: string | null;
21
+ after: string;
22
+ }>;
23
+ }
24
+
25
+ export function mergePageMetadataInPlace(
26
+ sourceCode: string,
27
+ newMeta: MetadataFields,
28
+ ): AstMergeResult {
29
+ const isClientComponent = /^\s*['"]use client['"]/m.test(sourceCode);
30
+ const hasGenerateMetadata = /\bexport\s+(async\s+)?function\s+generateMetadata\b/.test(sourceCode);
31
+
32
+ if (hasGenerateMetadata) {
33
+ return {
34
+ mergedCode: sourceCode,
35
+ modified: false,
36
+ hasComplexDynamicLogic: true,
37
+ isClientComponent,
38
+ changes: [],
39
+ };
40
+ }
41
+
42
+ const changes: Array<{ field: string; before?: string | null; after: string }> = [];
43
+
44
+ // Match existing `export const metadata = { ... }` or `export const metadata: Metadata = { ... }`
45
+ const metadataRegex = /(export\s+const\s+metadata(?:\s*:\s*\w+)?\s*=\s*\{)([\s\S]*?)(\};?)/m;
46
+ const match = metadataRegex.exec(sourceCode);
47
+
48
+ if (match) {
49
+ const prefix = match[1] ?? '';
50
+ let body = match[2] ?? '';
51
+ const suffix = match[3] ?? '';
52
+
53
+ // Check title
54
+ if (newMeta.title && !/\btitle\s*:/m.test(body)) {
55
+ const val = newMeta.title;
56
+ body = `\n title: '${val.replace(/'/g, "\\'")}',` + body;
57
+ changes.push({ field: 'title', before: null, after: val });
58
+ }
59
+
60
+ // Check description
61
+ if (newMeta.description && !/\bdescription\s*:/m.test(body)) {
62
+ const val = newMeta.description;
63
+ body = `\n description: '${val.replace(/'/g, "\\'")}',` + body;
64
+ changes.push({ field: 'description', before: null, after: val });
65
+ }
66
+
67
+ // Check alternates / canonical
68
+ if (newMeta.canonical && !/\balternates\s*:/m.test(body)) {
69
+ const val = newMeta.canonical;
70
+ body = body + `\n alternates: {\n canonical: '${val}',\n },`;
71
+ changes.push({ field: 'alternates.canonical', before: null, after: val });
72
+ }
73
+
74
+ // Check openGraph
75
+ if (newMeta.openGraph && !/\bopenGraph\s*:/m.test(body)) {
76
+ const ogTitle = newMeta.openGraph.title ?? newMeta.title ?? '';
77
+ const ogDesc = newMeta.openGraph.description ?? newMeta.description ?? '';
78
+ body = body + `\n openGraph: {\n title: '${ogTitle.replace(/'/g, "\\'")}',\n description: '${ogDesc.replace(/'/g, "\\'")}',\n },`;
79
+ changes.push({ field: 'openGraph', before: null, after: ogTitle });
80
+ }
81
+
82
+ const mergedCode = sourceCode.replace(metadataRegex, `${prefix}${body}\n${suffix}`);
83
+ return {
84
+ mergedCode,
85
+ modified: changes.length > 0,
86
+ hasComplexDynamicLogic: false,
87
+ isClientComponent,
88
+ changes,
89
+ };
90
+ }
91
+
92
+ // No existing metadata block: create one
93
+ const entries: string[] = [];
94
+ if (newMeta.title) {
95
+ entries.push(` title: '${newMeta.title.replace(/'/g, "\\'")}',`);
96
+ changes.push({ field: 'title', before: null, after: newMeta.title });
97
+ }
98
+ if (newMeta.description) {
99
+ entries.push(` description: '${newMeta.description.replace(/'/g, "\\'")}',`);
100
+ changes.push({ field: 'description', before: null, after: newMeta.description });
101
+ }
102
+ if (newMeta.canonical) {
103
+ entries.push(` alternates: {\n canonical: '${newMeta.canonical}',\n },`);
104
+ changes.push({ field: 'alternates.canonical', before: null, after: newMeta.canonical });
105
+ }
106
+ if (newMeta.openGraph) {
107
+ const ogTitle = newMeta.openGraph.title ?? newMeta.title ?? '';
108
+ const ogDesc = newMeta.openGraph.description ?? newMeta.description ?? '';
109
+ entries.push(` openGraph: {\n title: '${ogTitle.replace(/'/g, "\\'")}',\n description: '${ogDesc.replace(/'/g, "\\'")}',\n },`);
110
+ changes.push({ field: 'openGraph', before: null, after: ogTitle });
111
+ }
112
+
113
+ const newExport = `\nexport const metadata = {\n${entries.join('\n')}\n};\n`;
114
+ const mergedCode = sourceCode + newExport;
115
+
116
+ return {
117
+ mergedCode,
118
+ modified: true,
119
+ hasComplexDynamicLogic: false,
120
+ isClientComponent,
121
+ changes,
122
+ };
123
+ }
@@ -1,126 +1,155 @@
1
- import path from 'node:path';
2
- import type { DiscoveredRoute, ExtractedRouteData } from '@seo-partner/core';
3
- import type { AdapterCapabilities, AdapterVersion, FrameworkAdapter } from './contract.js';
4
- import { parseAstroOrMarkdown } from './parsers/astro-parser.js';
5
- import { normalizeRoutePattern } from './parsers/route-normalizer.js';
6
- import { LayoutResolver } from './parsers/layout-resolver.js';
7
-
8
- export class AstroAdapter implements FrameworkAdapter {
9
- public readonly id = 'astro';
10
- public readonly name = 'Astro';
11
- public readonly version: AdapterVersion = '1.0';
12
- public readonly capabilities: AdapterCapabilities = {
13
- routes: true,
14
- metadata: true,
15
- headings: true,
16
- internalLinks: true,
17
- jsonLd: true,
18
- images: true,
19
- robotsAndSitemap: true,
20
- };
21
-
22
- private readonly layoutResolver = new LayoutResolver();
23
-
24
- public async detect(
25
- projectRoot: string,
26
- filePaths: ReadonlyArray<string>,
27
- contentsByPath: ReadonlyMap<string, string>,
28
- ): Promise<boolean> {
29
- for (const filePath of filePaths) {
30
- const normalized = filePath.replace(/\\/g, '/');
31
- if (
32
- normalized === 'astro.config.mjs' ||
33
- normalized === 'astro.config.ts' ||
34
- normalized === 'astro.config.js' ||
35
- normalized === 'astro.config.cjs'
36
- ) {
37
- return true;
38
- }
39
- if (normalized === 'package.json') {
40
- const content = contentsByPath.get(filePath) ?? '';
41
- if (content.includes('"astro"') || content.includes('"@astrojs/')) {
42
- return true;
43
- }
44
- }
45
- }
46
- return false;
47
- }
48
-
49
- public async discoverRoutes(
50
- projectRoot: string,
51
- filePaths: ReadonlyArray<string>,
52
- contentsByPath: ReadonlyMap<string, string>,
53
- sitemapPaths: ReadonlySet<string>,
54
- ): Promise<ReadonlyArray<DiscoveredRoute>> {
55
- const routes: DiscoveredRoute[] = [];
56
-
57
- // Pre-cache layouts from src/layouts/**
58
- for (const filePath of filePaths) {
59
- const normalized = filePath.replace(/\\/g, '/');
60
- if (normalized.startsWith('src/layouts/') && normalized.endsWith('.astro')) {
61
- const content = contentsByPath.get(filePath) ?? '';
62
- const parsed = parseAstroOrMarkdown(
63
- {
64
- pattern: '',
65
- filePath,
66
- routerKind: 'astro',
67
- renderingMode: 'static',
68
- isPrivate: false,
69
- inSitemap: false,
70
- },
71
- content,
72
- );
73
- this.layoutResolver.registerLayout({
74
- filePath,
75
- htmlLang: parsed.routeData.htmlLang,
76
- title: parsed.routeData.title,
77
- description: parsed.routeData.description,
78
- canonical: parsed.routeData.canonical,
79
- robots: parsed.routeData.robots,
80
- });
81
- }
82
- }
83
-
84
- for (const filePath of filePaths) {
85
- const normalized = filePath.replace(/\\/g, '/');
86
- if (!normalized.startsWith('src/pages/')) continue;
87
- if (!/\.(astro|md|mdx|html)$/i.test(normalized)) continue;
88
-
89
- const relativeInsidePages = normalized.slice('src/pages/'.length);
90
- const pattern = normalizeRoutePattern(relativeInsidePages, 'astro');
91
- const inSitemap = sitemapPaths.has(pattern);
92
-
93
- routes.push({
94
- pattern,
95
- filePath,
96
- routerKind: 'astro',
97
- renderingMode: 'static',
98
- isPrivate: false,
99
- inSitemap,
100
- });
101
- }
102
-
103
- return routes;
104
- }
105
-
106
- public async extractRoute(
107
- projectRoot: string,
108
- route: DiscoveredRoute,
109
- fileContent: string,
110
- ): Promise<ExtractedRouteData> {
111
- const parsed = parseAstroOrMarkdown(route, fileContent);
112
- let extracted = parsed.routeData;
113
-
114
- // Apply layout inheritance: check layout candidates
115
- const layoutCandidates = [
116
- 'src/layouts/BaseLayout.astro',
117
- 'src/layouts/Layout.astro',
118
- 'src/layouts/MainLayout.astro',
119
- 'src/layouts/DefaultLayout.astro',
120
- ];
121
- extracted = this.layoutResolver.applyInheritance(extracted, layoutCandidates);
122
-
123
- return extracted;
124
- }
125
- }
126
-
1
+ import path from 'node:path';
2
+ import type { DiscoveredRoute, ExtractedRouteData } from '@seo-partner/core';
3
+ import type { AdapterCapabilities, AdapterVersion, FrameworkAdapter } from './contract.js';
4
+ import { parseAstroOrMarkdown } from './parsers/astro-parser.js';
5
+ import { normalizeRoutePattern } from './parsers/route-normalizer.js';
6
+ import { LayoutResolver } from './parsers/layout-resolver.js';
7
+
8
+ export class AstroAdapter implements FrameworkAdapter {
9
+ public readonly id = 'astro';
10
+ public readonly name = 'Astro';
11
+ public readonly version: AdapterVersion = '1.0';
12
+ public readonly capabilities: AdapterCapabilities = {
13
+ routes: true,
14
+ metadata: true,
15
+ headings: true,
16
+ internalLinks: true,
17
+ jsonLd: true,
18
+ images: true,
19
+ robotsAndSitemap: true,
20
+ };
21
+
22
+ private readonly layoutResolver = new LayoutResolver();
23
+
24
+ public async detect(
25
+ projectRoot: string,
26
+ filePaths: ReadonlyArray<string>,
27
+ contentsByPath: ReadonlyMap<string, string>,
28
+ ): Promise<boolean> {
29
+ for (const filePath of filePaths) {
30
+ const normalized = filePath.replace(/\\/g, '/');
31
+ if (
32
+ normalized === 'astro.config.mjs' ||
33
+ normalized === 'astro.config.ts' ||
34
+ normalized === 'astro.config.js' ||
35
+ normalized === 'astro.config.cjs'
36
+ ) {
37
+ return true;
38
+ }
39
+ if (normalized === 'package.json') {
40
+ const content = contentsByPath.get(filePath) ?? '';
41
+ if (content.includes('"astro"') || content.includes('"@astrojs/')) {
42
+ return true;
43
+ }
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+
49
+ public async discoverRoutes(
50
+ projectRoot: string,
51
+ filePaths: ReadonlyArray<string>,
52
+ contentsByPath: ReadonlyMap<string, string>,
53
+ sitemapPaths: ReadonlySet<string>,
54
+ ): Promise<ReadonlyArray<DiscoveredRoute>> {
55
+ const routes: DiscoveredRoute[] = [];
56
+
57
+ // Pre-cache layouts from src/layouts/**
58
+ for (const filePath of filePaths) {
59
+ const normalized = filePath.replace(/\\/g, '/');
60
+ if (normalized.startsWith('src/layouts/') && normalized.endsWith('.astro')) {
61
+ const content = contentsByPath.get(filePath) ?? '';
62
+ const parsed = parseAstroOrMarkdown(
63
+ {
64
+ pattern: '',
65
+ filePath,
66
+ routerKind: 'astro',
67
+ renderingMode: 'static',
68
+ isPrivate: false,
69
+ inSitemap: false,
70
+ },
71
+ content,
72
+ );
73
+ this.layoutResolver.registerLayout({
74
+ filePath,
75
+ htmlLang: parsed.routeData.htmlLang,
76
+ title: parsed.routeData.title,
77
+ description: parsed.routeData.description,
78
+ canonical: parsed.routeData.canonical,
79
+ robots: parsed.routeData.robots,
80
+ });
81
+ }
82
+ }
83
+
84
+ for (const filePath of filePaths) {
85
+ const normalized = filePath.replace(/\\/g, '/');
86
+ if (!normalized.startsWith('src/pages/')) continue;
87
+ if (!/\.(astro|md|mdx|html)$/i.test(normalized)) continue;
88
+
89
+ const relativeInsidePages = normalized.slice('src/pages/'.length);
90
+ const pattern = normalizeRoutePattern(relativeInsidePages, 'astro');
91
+ const inSitemap = sitemapPaths.has(pattern);
92
+
93
+ routes.push({
94
+ pattern,
95
+ filePath,
96
+ routerKind: 'astro',
97
+ renderingMode: 'static',
98
+ isPrivate: false,
99
+ inSitemap,
100
+ });
101
+ }
102
+
103
+ return routes;
104
+ }
105
+
106
+ public async extractRoute(
107
+ projectRoot: string,
108
+ route: DiscoveredRoute,
109
+ fileContent: string,
110
+ ): Promise<ExtractedRouteData> {
111
+ const parsed = parseAstroOrMarkdown(route, fileContent);
112
+ let extracted = parsed.routeData;
113
+
114
+ // Apply layout inheritance: check layout candidates
115
+ const layoutCandidates = [
116
+ 'src/layouts/BaseLayout.astro',
117
+ 'src/layouts/Layout.astro',
118
+ 'src/layouts/MainLayout.astro',
119
+ 'src/layouts/DefaultLayout.astro',
120
+ ];
121
+ extracted = this.layoutResolver.applyInheritance(extracted, layoutCandidates);
122
+
123
+ return extracted;
124
+ }
125
+ }
126
+
127
+
128
+ export interface AstroMetaFields {
129
+ title?: string;
130
+ description?: string;
131
+ canonical?: string;
132
+ }
133
+
134
+ export function patchAstroTemplate(
135
+ sourceCode: string,
136
+ meta: AstroMetaFields,
137
+ ): string {
138
+ let result = sourceCode;
139
+
140
+ const entries: string[] = [];
141
+ if (meta.title) entries.push(`title: "${meta.title.replace(/"/g, '\\"')}"`);
142
+ if (meta.description) entries.push(`description: "${meta.description.replace(/"/g, '\\"')}"`);
143
+ if (meta.canonical) entries.push(`canonical: "${meta.canonical}"`);
144
+
145
+ const seoVarBlock = `const seoMeta = {\n ${entries.join(',\n ')}\n};\n`;
146
+
147
+ const frontmatterMatch = /^---\s*\n([\s\S]*?)\n---/m.exec(result);
148
+ if (frontmatterMatch) {
149
+ result = result.replace(/^---\s*\n/m, `---\n${seoVarBlock}`);
150
+ } else {
151
+ result = `---\n${seoVarBlock}---\n\n` + result;
152
+ }
153
+
154
+ return result;
155
+ }