@growth-labs/seo 0.1.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 (158) hide show
  1. package/README.md +80 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +162 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/middleware/seo.d.ts +5 -0
  7. package/dist/middleware/seo.d.ts.map +1 -0
  8. package/dist/middleware/seo.js +70 -0
  9. package/dist/middleware/seo.js.map +1 -0
  10. package/dist/options.d.ts +489 -0
  11. package/dist/options.d.ts.map +1 -0
  12. package/dist/options.js +118 -0
  13. package/dist/options.js.map +1 -0
  14. package/dist/routes/llms.d.ts +4 -0
  15. package/dist/routes/llms.d.ts.map +1 -0
  16. package/dist/routes/llms.js +11 -0
  17. package/dist/routes/llms.js.map +1 -0
  18. package/dist/routes/podcast-narration.d.ts +4 -0
  19. package/dist/routes/podcast-narration.d.ts.map +1 -0
  20. package/dist/routes/podcast-narration.js +36 -0
  21. package/dist/routes/podcast-narration.js.map +1 -0
  22. package/dist/routes/podcast.d.ts +4 -0
  23. package/dist/routes/podcast.d.ts.map +1 -0
  24. package/dist/routes/podcast.js +20 -0
  25. package/dist/routes/podcast.js.map +1 -0
  26. package/dist/routes/robots.d.ts +4 -0
  27. package/dist/routes/robots.d.ts.map +1 -0
  28. package/dist/routes/robots.js +11 -0
  29. package/dist/routes/robots.js.map +1 -0
  30. package/dist/routes/rss.d.ts +4 -0
  31. package/dist/routes/rss.d.ts.map +1 -0
  32. package/dist/routes/rss.js +19 -0
  33. package/dist/routes/rss.js.map +1 -0
  34. package/dist/routes/sitemap-articles.d.ts +4 -0
  35. package/dist/routes/sitemap-articles.d.ts.map +1 -0
  36. package/dist/routes/sitemap-articles.js +19 -0
  37. package/dist/routes/sitemap-articles.js.map +1 -0
  38. package/dist/routes/sitemap-index.d.ts +4 -0
  39. package/dist/routes/sitemap-index.d.ts.map +1 -0
  40. package/dist/routes/sitemap-index.js +77 -0
  41. package/dist/routes/sitemap-index.js.map +1 -0
  42. package/dist/routes/sitemap-pages.d.ts +4 -0
  43. package/dist/routes/sitemap-pages.d.ts.map +1 -0
  44. package/dist/routes/sitemap-pages.js +18 -0
  45. package/dist/routes/sitemap-pages.js.map +1 -0
  46. package/dist/routes/sitemap-products.d.ts +4 -0
  47. package/dist/routes/sitemap-products.d.ts.map +1 -0
  48. package/dist/routes/sitemap-products.js +18 -0
  49. package/dist/routes/sitemap-products.js.map +1 -0
  50. package/dist/routes/sitemap-videos.d.ts +4 -0
  51. package/dist/routes/sitemap-videos.d.ts.map +1 -0
  52. package/dist/routes/sitemap-videos.js +18 -0
  53. package/dist/routes/sitemap-videos.js.map +1 -0
  54. package/dist/state.d.ts +7 -0
  55. package/dist/state.d.ts.map +1 -0
  56. package/dist/state.js +18 -0
  57. package/dist/state.js.map +1 -0
  58. package/dist/types.d.ts +103 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/utils/aeo.d.ts +16 -0
  63. package/dist/utils/aeo.d.ts.map +1 -0
  64. package/dist/utils/aeo.js +38 -0
  65. package/dist/utils/aeo.js.map +1 -0
  66. package/dist/utils/discover.d.ts +18 -0
  67. package/dist/utils/discover.d.ts.map +1 -0
  68. package/dist/utils/discover.js +41 -0
  69. package/dist/utils/discover.js.map +1 -0
  70. package/dist/utils/hreflang.d.ts +3 -0
  71. package/dist/utils/hreflang.d.ts.map +1 -0
  72. package/dist/utils/hreflang.js +20 -0
  73. package/dist/utils/hreflang.js.map +1 -0
  74. package/dist/utils/index.d.ts +12 -0
  75. package/dist/utils/index.d.ts.map +1 -0
  76. package/dist/utils/index.js +12 -0
  77. package/dist/utils/index.js.map +1 -0
  78. package/dist/utils/json-ld/article.d.ts +4 -0
  79. package/dist/utils/json-ld/article.d.ts.map +1 -0
  80. package/dist/utils/json-ld/article.js +66 -0
  81. package/dist/utils/json-ld/article.js.map +1 -0
  82. package/dist/utils/json-ld/audio.d.ts +7 -0
  83. package/dist/utils/json-ld/audio.d.ts.map +1 -0
  84. package/dist/utils/json-ld/audio.js +25 -0
  85. package/dist/utils/json-ld/audio.js.map +1 -0
  86. package/dist/utils/json-ld/breadcrumb.d.ts +7 -0
  87. package/dist/utils/json-ld/breadcrumb.d.ts.map +1 -0
  88. package/dist/utils/json-ld/breadcrumb.js +20 -0
  89. package/dist/utils/json-ld/breadcrumb.js.map +1 -0
  90. package/dist/utils/json-ld/faq.d.ts +6 -0
  91. package/dist/utils/json-ld/faq.d.ts.map +1 -0
  92. package/dist/utils/json-ld/faq.js +15 -0
  93. package/dist/utils/json-ld/faq.js.map +1 -0
  94. package/dist/utils/json-ld/howto.d.ts +7 -0
  95. package/dist/utils/json-ld/howto.d.ts.map +1 -0
  96. package/dist/utils/json-ld/howto.js +19 -0
  97. package/dist/utils/json-ld/howto.js.map +1 -0
  98. package/dist/utils/json-ld/index.d.ts +13 -0
  99. package/dist/utils/json-ld/index.d.ts.map +1 -0
  100. package/dist/utils/json-ld/index.js +12 -0
  101. package/dist/utils/json-ld/index.js.map +1 -0
  102. package/dist/utils/json-ld/item-list.d.ts +5 -0
  103. package/dist/utils/json-ld/item-list.d.ts.map +1 -0
  104. package/dist/utils/json-ld/item-list.js +20 -0
  105. package/dist/utils/json-ld/item-list.js.map +1 -0
  106. package/dist/utils/json-ld/organization.d.ts +4 -0
  107. package/dist/utils/json-ld/organization.d.ts.map +1 -0
  108. package/dist/utils/json-ld/organization.js +36 -0
  109. package/dist/utils/json-ld/organization.js.map +1 -0
  110. package/dist/utils/json-ld/person.d.ts +6 -0
  111. package/dist/utils/json-ld/person.d.ts.map +1 -0
  112. package/dist/utils/json-ld/person.js +23 -0
  113. package/dist/utils/json-ld/person.js.map +1 -0
  114. package/dist/utils/json-ld/product.d.ts +5 -0
  115. package/dist/utils/json-ld/product.d.ts.map +1 -0
  116. package/dist/utils/json-ld/product.js +112 -0
  117. package/dist/utils/json-ld/product.js.map +1 -0
  118. package/dist/utils/json-ld/video.d.ts +3 -0
  119. package/dist/utils/json-ld/video.d.ts.map +1 -0
  120. package/dist/utils/json-ld/video.js +20 -0
  121. package/dist/utils/json-ld/video.js.map +1 -0
  122. package/dist/utils/json-ld/website.d.ts +4 -0
  123. package/dist/utils/json-ld/website.d.ts.map +1 -0
  124. package/dist/utils/json-ld/website.js +22 -0
  125. package/dist/utils/json-ld/website.js.map +1 -0
  126. package/dist/utils/llms.d.ts +7 -0
  127. package/dist/utils/llms.d.ts.map +1 -0
  128. package/dist/utils/llms.js +38 -0
  129. package/dist/utils/llms.js.map +1 -0
  130. package/dist/utils/meta.d.ts +7 -0
  131. package/dist/utils/meta.d.ts.map +1 -0
  132. package/dist/utils/meta.js +122 -0
  133. package/dist/utils/meta.js.map +1 -0
  134. package/dist/utils/podcast.d.ts +8 -0
  135. package/dist/utils/podcast.d.ts.map +1 -0
  136. package/dist/utils/podcast.js +94 -0
  137. package/dist/utils/podcast.js.map +1 -0
  138. package/dist/utils/robots.d.ts +3 -0
  139. package/dist/utils/robots.d.ts.map +1 -0
  140. package/dist/utils/robots.js +28 -0
  141. package/dist/utils/robots.js.map +1 -0
  142. package/dist/utils/rss.d.ts +4 -0
  143. package/dist/utils/rss.d.ts.map +1 -0
  144. package/dist/utils/rss.js +53 -0
  145. package/dist/utils/rss.js.map +1 -0
  146. package/dist/utils/sitemap.d.ts +13 -0
  147. package/dist/utils/sitemap.d.ts.map +1 -0
  148. package/dist/utils/sitemap.js +133 -0
  149. package/dist/utils/sitemap.js.map +1 -0
  150. package/dist/utils/validation.d.ts +18 -0
  151. package/dist/utils/validation.d.ts.map +1 -0
  152. package/dist/utils/validation.js +141 -0
  153. package/dist/utils/validation.js.map +1 -0
  154. package/dist/vite-plugin.d.ts +4 -0
  155. package/dist/vite-plugin.d.ts.map +1 -0
  156. package/dist/vite-plugin.js +22 -0
  157. package/dist/vite-plugin.js.map +1 -0
  158. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @growth-labs/seo
2
+
3
+ Astro integration for complete SEO infrastructure on Cloudflare. Handles JSON-LD structured data, meta tags, sitemaps, RSS/podcast feeds, AEO (Answer Engine Optimization), multilingual support, robots.txt, llms.txt, and build-time validation.
4
+
5
+ ## Config
6
+
7
+ ```typescript
8
+ import seo from '@growth-labs/seo'
9
+
10
+ seo({
11
+ site: 'https://warfronts.channel',
12
+ schemaType: 'Article', // Default JSON-LD type
13
+ organization: {
14
+ name: 'WarFronts',
15
+ logo: 'https://media.warfronts.channel/logos/header.png',
16
+ sameAs: ['https://youtube.com/@warfronts'],
17
+ },
18
+ googleNews: false, // Include in Google News sitemap
19
+ aeoTwins: true, // Accept: text/markdown content negotiation
20
+ llmsTxt: true, // Serve /llms.txt
21
+ rss: true, // Serve /feed.xml
22
+ contentSignal: { // Content-Signal HTTP header
23
+ aiTrain: 'no',
24
+ search: 'yes',
25
+ aiInput: 'yes',
26
+ },
27
+ defaults: {
28
+ titleSuffix: ' | WarFronts',
29
+ twitterSite: '@warfronts',
30
+ },
31
+ // Optional:
32
+ commerce: { enabled: true, currency: 'USD', returnPolicy: { ... } },
33
+ podcast: { enabled: true, title: '...', author: '...', ... },
34
+ audioNarration: { enabled: true, narratorName: '...', asPodcastFeed: true },
35
+ locales: [{ lang: 'en', region: 'US', default: true }], // Multilingual
36
+ })
37
+ ```
38
+
39
+ ## What It Injects
40
+
41
+ **Middleware** (order: `post`):
42
+ - Adds `Content-Signal` header to responses
43
+ - Adds `Link` alternate header for hreflang
44
+ - Handles `Accept: text/markdown` → returns markdown twin (AEO)
45
+
46
+ **Routes:**
47
+ - `/sitemap-index.xml` — sitemap index
48
+ - `/sitemap-articles.xml`, `/sitemap-pages.xml`, `/sitemap-videos.xml`
49
+ - `/sitemap-products.xml` (if commerce enabled)
50
+ - `/robots.txt` — with AI crawler blocking directives
51
+ - `/llms.txt` (if enabled)
52
+ - `/feed.xml` (if RSS enabled)
53
+ - `/podcast.xml` (if podcast enabled)
54
+ - `/listen.xml` (if audio narration podcast feed enabled)
55
+
56
+ ## Standalone Utilities
57
+
58
+ Available without the integration:
59
+
60
+ ```typescript
61
+ import { generateJsonLd, generateMeta } from '@growth-labs/seo/utils'
62
+ import { articleJsonLd } from '@growth-labs/seo/utils/json-ld/article'
63
+ import { productJsonLd } from '@growth-labs/seo/utils/json-ld/product'
64
+ ```
65
+
66
+ **JSON-LD generators:** Article, NewsArticle, BlogPosting, FAQPage, VideoObject, AudioObject, Person, HowTo, Product, ProductGroup, Review, AggregateRating, BreadcrumbList, Organization, WebSite, ItemList, SpeakableSpecification
67
+
68
+ **Other utilities:** `generateMeta()` (OG + Twitter Card), sitemap XML, RSS/Atom, podcast RSS (iTunes namespace), hreflang tags, robots.txt, llms.txt
69
+
70
+ ## Wrangler Bindings
71
+
72
+ None required.
73
+
74
+ ## Key Patterns
75
+
76
+ - Virtual module: `virtual:growth-labs/seo/config`
77
+ - AI crawler blocking happens at Cloudflare CDN level, NOT in this package
78
+ - AEO twins: same content as markdown via `Accept: text/markdown` content negotiation
79
+ - Build-time validation warns on missing titles, descriptions, JSON-LD issues (non-blocking)
80
+ - `.astro` component files ship as source from `src/components/`
@@ -0,0 +1,8 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { type SeoOptions } from './options.js';
3
+ export default function seo(userOptions: SeoOptions): AstroIntegration;
4
+ export type { ResolvedSeoOptions, SeoOptions } from './options.js';
5
+ export { seoOptionsSchema } from './options.js';
6
+ export { getConfig, getContentProvider } from './state.js';
7
+ export type * from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,cAAc,CAAA;AAYhE,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,WAAW,EAAE,UAAU,GAAG,gBAAgB,CA+IrE;AAeD,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC1D,mBAAmB,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,162 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { seoOptionsSchema } from './options.js';
5
+ import { _setConfig, _setContentProvider } from './state.js';
6
+ import { SITEMAP_INDEX_PATH } from './utils/sitemap.js';
7
+ import { validateJsonLd, validatePage } from './utils/validation.js';
8
+ import { growthLabsSeoPlugin } from './vite-plugin.js';
9
+ const ENTRYPOINT_EXTENSION = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
10
+ function resolveEntrypoint(path) {
11
+ return fileURLToPath(new URL(`${path}${ENTRYPOINT_EXTENSION}`, import.meta.url));
12
+ }
13
+ export default function seo(userOptions) {
14
+ const options = seoOptionsSchema.parse(userOptions);
15
+ _setConfig(options);
16
+ if (userOptions.contentProvider) {
17
+ _setContentProvider(userOptions.contentProvider);
18
+ }
19
+ return {
20
+ name: '@growth-labs/seo',
21
+ hooks: {
22
+ 'astro:config:setup': ({ addMiddleware, injectRoute, updateConfig }) => {
23
+ updateConfig({ vite: { plugins: [growthLabsSeoPlugin(options)] } });
24
+ addMiddleware({ entrypoint: resolveEntrypoint('./middleware/seo'), order: 'post' });
25
+ // Sitemaps
26
+ injectRoute({
27
+ pattern: SITEMAP_INDEX_PATH,
28
+ entrypoint: resolveEntrypoint('./routes/sitemap-index'),
29
+ prerender: false,
30
+ });
31
+ injectRoute({
32
+ pattern: '/sitemap-articles.xml',
33
+ entrypoint: resolveEntrypoint('./routes/sitemap-articles'),
34
+ prerender: false,
35
+ });
36
+ injectRoute({
37
+ pattern: '/sitemap-pages.xml',
38
+ entrypoint: resolveEntrypoint('./routes/sitemap-pages'),
39
+ prerender: false,
40
+ });
41
+ injectRoute({
42
+ pattern: '/sitemap-videos.xml',
43
+ entrypoint: resolveEntrypoint('./routes/sitemap-videos'),
44
+ prerender: false,
45
+ });
46
+ if (options.commerce?.enabled) {
47
+ injectRoute({
48
+ pattern: '/sitemap-products.xml',
49
+ entrypoint: resolveEntrypoint('./routes/sitemap-products'),
50
+ prerender: false,
51
+ });
52
+ }
53
+ // Robots + llms
54
+ injectRoute({
55
+ pattern: '/robots.txt',
56
+ entrypoint: resolveEntrypoint('./routes/robots'),
57
+ prerender: false,
58
+ });
59
+ if (options.llmsTxt) {
60
+ injectRoute({
61
+ pattern: '/llms.txt',
62
+ entrypoint: resolveEntrypoint('./routes/llms'),
63
+ prerender: false,
64
+ });
65
+ }
66
+ // RSS
67
+ if (options.rss) {
68
+ injectRoute({
69
+ pattern: '/feed.xml',
70
+ entrypoint: resolveEntrypoint('./routes/rss'),
71
+ prerender: false,
72
+ });
73
+ }
74
+ // Podcast
75
+ if (options.podcast?.enabled) {
76
+ injectRoute({
77
+ pattern: options.podcast.feedPath,
78
+ entrypoint: resolveEntrypoint('./routes/podcast'),
79
+ prerender: false,
80
+ });
81
+ }
82
+ // Narrated articles podcast feed
83
+ if (options.audioNarration?.asPodcastFeed) {
84
+ injectRoute({
85
+ pattern: options.audioNarration.podcastFeedPath,
86
+ entrypoint: resolveEntrypoint('./routes/podcast-narration'),
87
+ prerender: false,
88
+ });
89
+ }
90
+ },
91
+ 'astro:build:done': async ({ dir, logger }) => {
92
+ if (!options.validation.enabled)
93
+ return;
94
+ const outDir = fileURLToPath(dir);
95
+ const htmlFiles = findHtmlFiles(outDir);
96
+ let errorCount = 0;
97
+ let warningCount = 0;
98
+ for (const file of htmlFiles) {
99
+ const html = readFileSync(file, 'utf-8');
100
+ const result = validatePage(html, {
101
+ titleMaxLength: options.validation.titleMaxLength,
102
+ descriptionMaxLength: options.validation.descriptionMaxLength,
103
+ heroMinWidth: options.validation.heroMinWidth,
104
+ });
105
+ const relPath = file.replace(outDir, '');
106
+ for (const error of result.errors) {
107
+ logger.error(`${relPath}: ${error}`);
108
+ errorCount++;
109
+ }
110
+ for (const warning of result.warnings) {
111
+ logger.warn(`${relPath}: ${warning}`);
112
+ warningCount++;
113
+ }
114
+ // Validate JSON-LD blocks
115
+ const jsonLdMatches = html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/gi);
116
+ for (const match of jsonLdMatches) {
117
+ try {
118
+ const parsed = JSON.parse(match[1]);
119
+ const ldResult = validateJsonLd(parsed);
120
+ for (const e of ldResult.errors) {
121
+ logger.error(`${relPath} [JSON-LD]: ${e}`);
122
+ errorCount++;
123
+ }
124
+ for (const w of ldResult.warnings) {
125
+ logger.warn(`${relPath} [JSON-LD]: ${w}`);
126
+ warningCount++;
127
+ }
128
+ }
129
+ catch {
130
+ logger.error(`${relPath}: Invalid JSON-LD`);
131
+ errorCount++;
132
+ }
133
+ }
134
+ }
135
+ if (errorCount || warningCount) {
136
+ logger.info(`SEO validation: ${errorCount} errors, ${warningCount} warnings`);
137
+ }
138
+ else {
139
+ logger.info('SEO validation: all checks passed');
140
+ }
141
+ },
142
+ },
143
+ };
144
+ }
145
+ function findHtmlFiles(dir) {
146
+ const results = [];
147
+ try {
148
+ const entries = readdirSync(dir, { withFileTypes: true });
149
+ for (const entry of entries) {
150
+ const fullPath = join(dir, entry.name);
151
+ if (entry.isDirectory())
152
+ results.push(...findHtmlFiles(fullPath));
153
+ else if (entry.name.endsWith('.html'))
154
+ results.push(fullPath);
155
+ }
156
+ }
157
+ catch { }
158
+ return results;
159
+ }
160
+ export { seoOptionsSchema } from './options.js';
161
+ export { getConfig, getContentProvider } from './state.js';
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { ResolvedSeoOptions } from '../options.js';
2
+ import type { ContentProvider } from '../types.js';
3
+ export declare function createSeoMiddleware(options: ResolvedSeoOptions, contentProvider?: ContentProvider): (context: any, next: () => Promise<Response>) => Promise<Response>;
4
+ export declare const onRequest: (context: any, next: () => Promise<Response>) => Promise<Response>;
5
+ //# sourceMappingURL=seo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/middleware/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AASlD,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,kBAAkB,EAC3B,eAAe,CAAC,EAAE,eAAe,IAEnB,SAAS,GAAG,EAAE,MAAM,MAAM,OAAO,CAAC,QAAQ,CAAC,KAAG,OAAO,CAAC,QAAQ,CAAC,CAgE7E;AAGD,eAAO,MAAM,SAAS,GAAU,SAAS,GAAG,EAAE,MAAM,MAAM,OAAO,CAAC,QAAQ,CAAC,sBAK1E,CAAA"}
@@ -0,0 +1,70 @@
1
+ import { getConfig, getContentProvider } from '../state.js';
2
+ import { estimateTokenCount, generateAeoMarkdown } from '../utils/aeo.js';
3
+ function buildContentSignalHeader(config) {
4
+ const { aiTrain, search, aiInput } = config.contentSignal;
5
+ return `ai-train=${aiTrain}, search=${search}, ai-input=${aiInput}`;
6
+ }
7
+ // Factory for testability
8
+ export function createSeoMiddleware(options, contentProvider) {
9
+ return async (context, next) => {
10
+ const { request } = context;
11
+ const accept = request.headers.get('accept') ?? '';
12
+ const url = new URL(request.url);
13
+ // AEO content negotiation: intercept Accept: text/markdown requests
14
+ if (options.aeoTwins && accept.includes('text/markdown') && contentProvider) {
15
+ try {
16
+ const articles = await contentProvider({ type: 'articles' }, context);
17
+ const matched = articles.find((a) => {
18
+ try {
19
+ return new URL(a.url).pathname === url.pathname;
20
+ }
21
+ catch {
22
+ return a.url === url.pathname;
23
+ }
24
+ });
25
+ if (matched) {
26
+ const content = matched.description ?? '';
27
+ const markdown = generateAeoMarkdown(matched, content, options.schemaType, options.organization.name);
28
+ const tokenCount = estimateTokenCount(markdown);
29
+ const contentSignal = buildContentSignalHeader(options);
30
+ return new Response(markdown, {
31
+ headers: {
32
+ 'Content-Type': 'text/markdown; charset=utf-8',
33
+ 'x-markdown-tokens': String(tokenCount),
34
+ 'content-signal': contentSignal,
35
+ },
36
+ });
37
+ }
38
+ }
39
+ catch { }
40
+ }
41
+ // Pass through to next middleware/route handler
42
+ const response = await next();
43
+ // Clone to add headers (Response headers are immutable)
44
+ const newHeaders = new Headers(response.headers);
45
+ // Add Content-Signal header to every response
46
+ newHeaders.set('content-signal', buildContentSignalHeader(options));
47
+ // Add Link alternate header on HTML responses when aeoTwins enabled
48
+ if (options.aeoTwins) {
49
+ const contentType = response.headers.get('content-type') ?? '';
50
+ if (contentType.includes('text/html')) {
51
+ const linkValue = `<${url.toString()}>; rel="alternate"; type="text/markdown"`;
52
+ const existing = newHeaders.get('Link');
53
+ newHeaders.set('Link', existing ? `${existing}, ${linkValue}` : linkValue);
54
+ }
55
+ }
56
+ return new Response(response.body, {
57
+ status: response.status,
58
+ statusText: response.statusText,
59
+ headers: newHeaders,
60
+ });
61
+ };
62
+ }
63
+ // Astro middleware entry point
64
+ export const onRequest = async (context, next) => {
65
+ const config = getConfig();
66
+ const contentProvider = getContentProvider();
67
+ const middleware = createSeoMiddleware(config, contentProvider);
68
+ return middleware(context, next);
69
+ };
70
+ //# sourceMappingURL=seo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo.js","sourceRoot":"","sources":["../../src/middleware/seo.ts"],"names":[],"mappings":"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"}