@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.
- package/dist/ast-merger.d.ts +24 -0
- package/dist/ast-merger.d.ts.map +1 -0
- package/dist/ast-merger.js +85 -0
- package/dist/ast-merger.js.map +1 -0
- package/dist/astro-adapter.d.ts +6 -0
- package/dist/astro-adapter.d.ts.map +1 -1
- package/dist/astro-adapter.js +19 -0
- package/dist/astro-adapter.js.map +1 -1
- package/dist/blog-scaffold-generator.d.ts +8 -0
- package/dist/blog-scaffold-generator.d.ts.map +1 -0
- package/dist/blog-scaffold-generator.js +138 -0
- package/dist/blog-scaffold-generator.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/laravel-adapter.d.ts +7 -0
- package/dist/laravel-adapter.d.ts.map +1 -0
- package/dist/laravel-adapter.js +35 -0
- package/dist/laravel-adapter.js.map +1 -0
- package/dist/layout-title-resolver.d.ts +8 -0
- package/dist/layout-title-resolver.d.ts.map +1 -0
- package/dist/layout-title-resolver.js +28 -0
- package/dist/layout-title-resolver.js.map +1 -0
- package/dist/nuxt-adapter.d.ts +6 -0
- package/dist/nuxt-adapter.d.ts.map +1 -1
- package/dist/nuxt-adapter.js +20 -0
- package/dist/nuxt-adapter.js.map +1 -1
- package/dist/php-native-adapter.d.ts +7 -0
- package/dist/php-native-adapter.d.ts.map +1 -0
- package/dist/php-native-adapter.js +31 -0
- package/dist/php-native-adapter.js.map +1 -0
- package/dist/sitemap-builder.d.ts +10 -0
- package/dist/sitemap-builder.d.ts.map +1 -0
- package/dist/sitemap-builder.js +37 -0
- package/dist/sitemap-builder.js.map +1 -0
- package/dist/skeleton-detector.d.ts +14 -0
- package/dist/skeleton-detector.d.ts.map +1 -0
- package/dist/skeleton-detector.js +33 -0
- package/dist/skeleton-detector.js.map +1 -0
- package/dist/sveltekit-adapter.d.ts +6 -0
- package/dist/sveltekit-adapter.d.ts.map +1 -1
- package/dist/sveltekit-adapter.js +26 -0
- package/dist/sveltekit-adapter.js.map +1 -1
- package/dist/symfony-adapter.d.ts +7 -0
- package/dist/symfony-adapter.d.ts.map +1 -0
- package/dist/symfony-adapter.js +23 -0
- package/dist/symfony-adapter.js.map +1 -0
- package/package.json +2 -2
- package/src/ast-merger.ts +123 -0
- package/src/astro-adapter.ts +155 -126
- package/src/blog-scaffold-generator.ts +153 -0
- package/src/index.ts +8 -0
- package/src/laravel-adapter.ts +45 -0
- package/src/layout-title-resolver.ts +37 -0
- package/src/nuxt-adapter.ts +148 -118
- package/src/php-native-adapter.ts +46 -0
- package/src/sitemap-builder.ts +57 -0
- package/src/skeleton-detector.ts +56 -0
- package/src/sveltekit-adapter.ts +160 -119
- 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, '"')}" />\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, '"')}" />`);
|
|
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":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,MAAM,OAAO,gBAAgB;IACX,EAAE,GAAG,WAAW,CAAC;IACjB,IAAI,GAAG,WAAW,CAAC;IACnB,OAAO,GAAmB,KAAK,CAAC;IAChC,YAAY,GAAwB;QAClD,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI;QACZ,gBAAgB,EAAE,IAAI;KACvB,CAAC;IAEe,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;IAEhD,KAAK,CAAC,MAAM,CACjB,WAAmB,EACnB,SAAgC,EAChC,cAA2C;QAE3C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAChD,IACE,UAAU,KAAK,kBAAkB;gBACjC,UAAU,KAAK,kBAAkB;gBACjC,UAAU,KAAK,mBAAmB;gBAClC,UAAU,KAAK,mBAAmB,EAClC,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,UAAU,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBACxC,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEM,KAAK,CAAC,cAAc,CACzB,WAAmB,EACnB,SAAgC,EAChC,cAA2C,EAC3C,YAAiC;QAEjC,MAAM,MAAM,GAAsB,EAAE,CAAC;QAErC,sDAAsD;QACtD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAChD,IAAI,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAClF,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnD,MAAM,MAAM,GAAG,oBAAoB,CACjC;oBACE,OAAO,EAAE,EAAE;oBACX,QAAQ;oBACR,UAAU,EAAE,WAAW;oBACvB,aAAa,EAAE,SAAS;oBACxB,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,KAAK;iBACjB,EACD,OAAO,CACR,CAAC;gBACF,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC;oBACjC,QAAQ;oBACR,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC;gBAAE,SAAS;YACpD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAAE,SAAS;YAEnD,MAAM,oBAAoB,GAAG,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACpE,MAAM,OAAO,GAAG,qBAAqB,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;YACzE,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAE5C,MAAM,CAAC,IAAI,CAAC;gBACV,OAAO;gBACP,QAAQ;gBACR,UAAU,EAAE,WAAW;gBACvB,aAAa,EAAE,SAAS;gBACxB,SAAS,EAAE,KAAK;gBAChB,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAEM,KAAK,CAAC,YAAY,CACvB,WAAmB,EACnB,KAAsB,EACtB,WAAmB;QAEnB,IAAI,SAAS,GAAG,oBAAoB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAEzD,6DAA6D;QAC7D,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,wBAAwB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACnF,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAE3E,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"sveltekit-adapter.js","sourceRoot":"","sources":["../src/sveltekit-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -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, '"')}">\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.
|
|
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.
|
|
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
|
+
}
|
package/src/astro-adapter.ts
CHANGED
|
@@ -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
|
+
}
|