@publish.os/nextjs 0.0.1-alpha.3 → 0.0.1-alpha.4

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/README.md CHANGED
@@ -1,14 +1,8 @@
1
1
  # @publish.os/nextjs
2
2
 
3
- PublishOS runtime package for generated Next.js applications.
4
-
5
- NextJS runtime for your `publish.os/` content.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pnpm add @publish.os/nextjs
11
- ```
3
+ PublishOS runtime package for generated Next.js applications. Tenant source
4
+ pins its exact version in `publish.os.config.json`; the CLI obtains and verifies
5
+ it automatically. Do not install it into the host application.
12
6
 
13
7
  ## License
14
8
 
package/dist/index.cjs CHANGED
@@ -178,22 +178,28 @@ function cachedTextHeaders(contentType, options = {}) {
178
178
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
179
179
  headers.set("content-type", contentType);
180
180
  headers.set("x-content-type-options", "nosniff");
181
- if (options.etag) headers.set("etag", options.etag);
182
181
  if (options.noindex) headers.set("x-robots-tag", "noindex");
183
182
  return headers;
184
183
  }
185
184
  function createCachedTextResponse(options) {
186
185
  const status = options.status ?? 200;
187
- const method = options.method ?? options.request?.method ?? "GET";
186
+ const method = options.method ?? "GET";
188
187
  const headers = cachedTextHeaders(options.contentType, options);
189
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
190
- return new Response(
191
- method === "HEAD" || isNotModified ? null : options.body,
192
- {
193
- headers,
194
- status: isNotModified ? 304 : status
195
- }
196
- );
188
+ return new Response(method === "HEAD" ? null : options.body, {
189
+ headers,
190
+ status
191
+ });
192
+ }
193
+
194
+ // src/kernel/routes/metadata/canonical-url.ts
195
+ function canonicalUrl(manifest, path) {
196
+ const origin = manifest.site.url?.replace(/\/+$/, "");
197
+ if (!origin) return path;
198
+ try {
199
+ return new URL(path, `${origin}/`).toString();
200
+ } catch {
201
+ return path;
202
+ }
197
203
  }
198
204
 
199
205
  // src/kernel/routes/metadata/feed.ts
@@ -206,15 +212,6 @@ function pageTimestamp(page) {
206
212
  const timestamp = Date.parse(value);
207
213
  return Number.isNaN(timestamp) ? -Infinity : timestamp;
208
214
  }
209
- function canonicalUrl(manifest, path) {
210
- const origin = manifest.site.url?.replace(/\/+$/, "");
211
- if (!origin) return path;
212
- try {
213
- return new URL(path, `${origin}/`).toString();
214
- } catch {
215
- return path;
216
- }
217
- }
218
215
  function pageInScope(page, options) {
219
216
  if (options.scope === "site" || !options.group) return true;
220
217
  if (options.scope === "self") return page.group === options.group;
@@ -379,10 +376,10 @@ async function llmsTxt(manifest, options = {}) {
379
376
  const visiblePages = options.limit ? pages.slice(0, options.limit) : pages;
380
377
  const lines = visiblePages.map((page) => {
381
378
  const description = options.includeDescriptions && page.description ? ` - ${page.description}` : "";
382
- return `- [${page.title}](${page.path})${description}`;
379
+ return `- [${page.title}](${canonicalUrl(manifest, page.path)})${description}`;
383
380
  });
384
381
  const linkLines = (options.links ?? []).map(
385
- (link) => `- [${link.title}](${link.path})`
382
+ (link) => `- [${link.title}](${canonicalUrl(manifest, link.path)})`
386
383
  );
387
384
  const linkSection = linkLines.length ? `
388
385
 
@@ -1012,7 +1009,11 @@ async function robotsTxt(manifest) {
1012
1009
  "use cache";
1013
1010
  (0, import_cache3.cacheLife)("page");
1014
1011
  (0, import_cache3.cacheTag)(tags.site(), tags.robots(), tags.manifest(manifest.contentHash));
1015
- return "User-agent: *\nAllow: /\nSitemap: /sitemap.xml\n";
1012
+ const sitemapUrl = canonicalUrl(manifest, "/sitemap.xml");
1013
+ return `User-agent: *
1014
+ Allow: /
1015
+ Sitemap: ${sitemapUrl}
1016
+ `;
1016
1017
  }
1017
1018
  function createRobotsTxtRoute(manifest) {
1018
1019
  return async function GET() {
@@ -1038,7 +1039,9 @@ async function sitemapXml(manifest) {
1038
1039
  ...manifest.pages.filter((page) => page.status === "published").map((page) => page.path)
1039
1040
  ];
1040
1041
  const uniqueUrls = [...new Set(urls)];
1041
- const body = uniqueUrls.map((url) => ` <url><loc>${xmlEscape2(url)}</loc></url>`).join("\n");
1042
+ const body = uniqueUrls.map(
1043
+ (url) => ` <url><loc>${xmlEscape2(canonicalUrl(manifest, url))}</loc></url>`
1044
+ ).join("\n");
1042
1045
  return `<?xml version="1.0" encoding="UTF-8"?>
1043
1046
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1044
1047
  ${body}
package/dist/index.js CHANGED
@@ -132,22 +132,28 @@ function cachedTextHeaders(contentType, options = {}) {
132
132
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
133
133
  headers.set("content-type", contentType);
134
134
  headers.set("x-content-type-options", "nosniff");
135
- if (options.etag) headers.set("etag", options.etag);
136
135
  if (options.noindex) headers.set("x-robots-tag", "noindex");
137
136
  return headers;
138
137
  }
139
138
  function createCachedTextResponse(options) {
140
139
  const status = options.status ?? 200;
141
- const method = options.method ?? options.request?.method ?? "GET";
140
+ const method = options.method ?? "GET";
142
141
  const headers = cachedTextHeaders(options.contentType, options);
143
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
144
- return new Response(
145
- method === "HEAD" || isNotModified ? null : options.body,
146
- {
147
- headers,
148
- status: isNotModified ? 304 : status
149
- }
150
- );
142
+ return new Response(method === "HEAD" ? null : options.body, {
143
+ headers,
144
+ status
145
+ });
146
+ }
147
+
148
+ // src/kernel/routes/metadata/canonical-url.ts
149
+ function canonicalUrl(manifest, path) {
150
+ const origin = manifest.site.url?.replace(/\/+$/, "");
151
+ if (!origin) return path;
152
+ try {
153
+ return new URL(path, `${origin}/`).toString();
154
+ } catch {
155
+ return path;
156
+ }
151
157
  }
152
158
 
153
159
  // src/kernel/routes/metadata/feed.ts
@@ -160,15 +166,6 @@ function pageTimestamp(page) {
160
166
  const timestamp = Date.parse(value);
161
167
  return Number.isNaN(timestamp) ? -Infinity : timestamp;
162
168
  }
163
- function canonicalUrl(manifest, path) {
164
- const origin = manifest.site.url?.replace(/\/+$/, "");
165
- if (!origin) return path;
166
- try {
167
- return new URL(path, `${origin}/`).toString();
168
- } catch {
169
- return path;
170
- }
171
- }
172
169
  function pageInScope(page, options) {
173
170
  if (options.scope === "site" || !options.group) return true;
174
171
  if (options.scope === "self") return page.group === options.group;
@@ -333,10 +330,10 @@ async function llmsTxt(manifest, options = {}) {
333
330
  const visiblePages = options.limit ? pages.slice(0, options.limit) : pages;
334
331
  const lines = visiblePages.map((page) => {
335
332
  const description = options.includeDescriptions && page.description ? ` - ${page.description}` : "";
336
- return `- [${page.title}](${page.path})${description}`;
333
+ return `- [${page.title}](${canonicalUrl(manifest, page.path)})${description}`;
337
334
  });
338
335
  const linkLines = (options.links ?? []).map(
339
- (link) => `- [${link.title}](${link.path})`
336
+ (link) => `- [${link.title}](${canonicalUrl(manifest, link.path)})`
340
337
  );
341
338
  const linkSection = linkLines.length ? `
342
339
 
@@ -966,7 +963,11 @@ async function robotsTxt(manifest) {
966
963
  "use cache";
967
964
  cacheLife3("page");
968
965
  cacheTag3(tags.site(), tags.robots(), tags.manifest(manifest.contentHash));
969
- return "User-agent: *\nAllow: /\nSitemap: /sitemap.xml\n";
966
+ const sitemapUrl = canonicalUrl(manifest, "/sitemap.xml");
967
+ return `User-agent: *
968
+ Allow: /
969
+ Sitemap: ${sitemapUrl}
970
+ `;
970
971
  }
971
972
  function createRobotsTxtRoute(manifest) {
972
973
  return async function GET() {
@@ -992,7 +993,9 @@ async function sitemapXml(manifest) {
992
993
  ...manifest.pages.filter((page) => page.status === "published").map((page) => page.path)
993
994
  ];
994
995
  const uniqueUrls = [...new Set(urls)];
995
- const body = uniqueUrls.map((url) => ` <url><loc>${xmlEscape2(url)}</loc></url>`).join("\n");
996
+ const body = uniqueUrls.map(
997
+ (url) => ` <url><loc>${xmlEscape2(canonicalUrl(manifest, url))}</loc></url>`
998
+ ).join("\n");
996
999
  return `<?xml version="1.0" encoding="UTF-8"?>
997
1000
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
998
1001
  ${body}
@@ -65,22 +65,17 @@ function cachedTextHeaders(contentType, options = {}) {
65
65
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
66
66
  headers.set("content-type", contentType);
67
67
  headers.set("x-content-type-options", "nosniff");
68
- if (options.etag) headers.set("etag", options.etag);
69
68
  if (options.noindex) headers.set("x-robots-tag", "noindex");
70
69
  return headers;
71
70
  }
72
71
  function createCachedTextResponse(options) {
73
72
  const status = options.status ?? 200;
74
- const method = options.method ?? options.request?.method ?? "GET";
73
+ const method = options.method ?? "GET";
75
74
  const headers = cachedTextHeaders(options.contentType, options);
76
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
77
- return new Response(
78
- method === "HEAD" || isNotModified ? null : options.body,
79
- {
80
- headers,
81
- status: isNotModified ? 304 : status
82
- }
83
- );
75
+ return new Response(method === "HEAD" ? null : options.body, {
76
+ headers,
77
+ status
78
+ });
84
79
  }
85
80
 
86
81
  // src/kernel/routes/agent/markdown.ts
@@ -181,7 +176,6 @@ async function markdownResponse(artifacts, discoveryLinks, method, context) {
181
176
  return createCachedTextResponse({
182
177
  body: artifact.body,
183
178
  contentType,
184
- etag: artifact.etag,
185
179
  headers,
186
180
  method,
187
181
  status: 200
@@ -2,7 +2,6 @@ type MarkdownRouteArtifact = {
2
2
  body: string;
3
3
  bodyHash?: string;
4
4
  byteSize?: number;
5
- etag: string;
6
5
  htmlPath: string;
7
6
  image?: string;
8
7
  markdownPath: string;
@@ -2,7 +2,6 @@ type MarkdownRouteArtifact = {
2
2
  body: string;
3
3
  bodyHash?: string;
4
4
  byteSize?: number;
5
- etag: string;
6
5
  htmlPath: string;
7
6
  image?: string;
8
7
  markdownPath: string;
@@ -38,22 +38,17 @@ function cachedTextHeaders(contentType, options = {}) {
38
38
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
39
39
  headers.set("content-type", contentType);
40
40
  headers.set("x-content-type-options", "nosniff");
41
- if (options.etag) headers.set("etag", options.etag);
42
41
  if (options.noindex) headers.set("x-robots-tag", "noindex");
43
42
  return headers;
44
43
  }
45
44
  function createCachedTextResponse(options) {
46
45
  const status = options.status ?? 200;
47
- const method = options.method ?? options.request?.method ?? "GET";
46
+ const method = options.method ?? "GET";
48
47
  const headers = cachedTextHeaders(options.contentType, options);
49
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
50
- return new Response(
51
- method === "HEAD" || isNotModified ? null : options.body,
52
- {
53
- headers,
54
- status: isNotModified ? 304 : status
55
- }
56
- );
48
+ return new Response(method === "HEAD" ? null : options.body, {
49
+ headers,
50
+ status
51
+ });
57
52
  }
58
53
 
59
54
  // src/kernel/routes/agent/markdown.ts
@@ -154,7 +149,6 @@ async function markdownResponse(artifacts, discoveryLinks, method, context) {
154
149
  return createCachedTextResponse({
155
150
  body: artifact.body,
156
151
  contentType,
157
- etag: artifact.etag,
158
152
  headers,
159
153
  method,
160
154
  status: 200
@@ -102,22 +102,28 @@ function cachedTextHeaders(contentType, options = {}) {
102
102
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
103
103
  headers.set("content-type", contentType);
104
104
  headers.set("x-content-type-options", "nosniff");
105
- if (options.etag) headers.set("etag", options.etag);
106
105
  if (options.noindex) headers.set("x-robots-tag", "noindex");
107
106
  return headers;
108
107
  }
109
108
  function createCachedTextResponse(options) {
110
109
  const status = options.status ?? 200;
111
- const method = options.method ?? options.request?.method ?? "GET";
110
+ const method = options.method ?? "GET";
112
111
  const headers = cachedTextHeaders(options.contentType, options);
113
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
114
- return new Response(
115
- method === "HEAD" || isNotModified ? null : options.body,
116
- {
117
- headers,
118
- status: isNotModified ? 304 : status
119
- }
120
- );
112
+ return new Response(method === "HEAD" ? null : options.body, {
113
+ headers,
114
+ status
115
+ });
116
+ }
117
+
118
+ // src/kernel/routes/metadata/canonical-url.ts
119
+ function canonicalUrl(manifest, path) {
120
+ const origin = manifest.site.url?.replace(/\/+$/, "");
121
+ if (!origin) return path;
122
+ try {
123
+ return new URL(path, `${origin}/`).toString();
124
+ } catch {
125
+ return path;
126
+ }
121
127
  }
122
128
 
123
129
  // src/kernel/routes/metadata/feed.ts
@@ -130,15 +136,6 @@ function pageTimestamp(page) {
130
136
  const timestamp = Date.parse(value);
131
137
  return Number.isNaN(timestamp) ? -Infinity : timestamp;
132
138
  }
133
- function canonicalUrl(manifest, path) {
134
- const origin = manifest.site.url?.replace(/\/+$/, "");
135
- if (!origin) return path;
136
- try {
137
- return new URL(path, `${origin}/`).toString();
138
- } catch {
139
- return path;
140
- }
141
- }
142
139
  function pageInScope(page, options) {
143
140
  if (options.scope === "site" || !options.group) return true;
144
141
  if (options.scope === "self") return page.group === options.group;
@@ -283,8 +280,7 @@ function createPrecomputedFeedRoute(output) {
283
280
  return function GET() {
284
281
  return createCachedTextResponse({
285
282
  body: output.body,
286
- contentType: output.contentType,
287
- etag: output.etag
283
+ contentType: output.contentType
288
284
  });
289
285
  };
290
286
  }
@@ -11,7 +11,6 @@ type FeedRouteOptions = {
11
11
  type PrecomputedFeedOutput = {
12
12
  body: string;
13
13
  contentType: string;
14
- etag?: string;
15
14
  };
16
15
  declare function createRssFeedRoute(manifest: ManifestLike, options?: FeedRouteOptions): () => Promise<Response>;
17
16
  declare function createAtomFeedRoute(manifest: ManifestLike, options?: FeedRouteOptions): () => Promise<Response>;
@@ -11,7 +11,6 @@ type FeedRouteOptions = {
11
11
  type PrecomputedFeedOutput = {
12
12
  body: string;
13
13
  contentType: string;
14
- etag?: string;
15
14
  };
16
15
  declare function createRssFeedRoute(manifest: ManifestLike, options?: FeedRouteOptions): () => Promise<Response>;
17
16
  declare function createAtomFeedRoute(manifest: ManifestLike, options?: FeedRouteOptions): () => Promise<Response>;
@@ -75,22 +75,28 @@ function cachedTextHeaders(contentType, options = {}) {
75
75
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
76
76
  headers.set("content-type", contentType);
77
77
  headers.set("x-content-type-options", "nosniff");
78
- if (options.etag) headers.set("etag", options.etag);
79
78
  if (options.noindex) headers.set("x-robots-tag", "noindex");
80
79
  return headers;
81
80
  }
82
81
  function createCachedTextResponse(options) {
83
82
  const status = options.status ?? 200;
84
- const method = options.method ?? options.request?.method ?? "GET";
83
+ const method = options.method ?? "GET";
85
84
  const headers = cachedTextHeaders(options.contentType, options);
86
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
87
- return new Response(
88
- method === "HEAD" || isNotModified ? null : options.body,
89
- {
90
- headers,
91
- status: isNotModified ? 304 : status
92
- }
93
- );
85
+ return new Response(method === "HEAD" ? null : options.body, {
86
+ headers,
87
+ status
88
+ });
89
+ }
90
+
91
+ // src/kernel/routes/metadata/canonical-url.ts
92
+ function canonicalUrl(manifest, path) {
93
+ const origin = manifest.site.url?.replace(/\/+$/, "");
94
+ if (!origin) return path;
95
+ try {
96
+ return new URL(path, `${origin}/`).toString();
97
+ } catch {
98
+ return path;
99
+ }
94
100
  }
95
101
 
96
102
  // src/kernel/routes/metadata/feed.ts
@@ -103,15 +109,6 @@ function pageTimestamp(page) {
103
109
  const timestamp = Date.parse(value);
104
110
  return Number.isNaN(timestamp) ? -Infinity : timestamp;
105
111
  }
106
- function canonicalUrl(manifest, path) {
107
- const origin = manifest.site.url?.replace(/\/+$/, "");
108
- if (!origin) return path;
109
- try {
110
- return new URL(path, `${origin}/`).toString();
111
- } catch {
112
- return path;
113
- }
114
- }
115
112
  function pageInScope(page, options) {
116
113
  if (options.scope === "site" || !options.group) return true;
117
114
  if (options.scope === "self") return page.group === options.group;
@@ -256,8 +253,7 @@ function createPrecomputedFeedRoute(output) {
256
253
  return function GET() {
257
254
  return createCachedTextResponse({
258
255
  body: output.body,
259
- contentType: output.contentType,
260
- etag: output.etag
256
+ contentType: output.contentType
261
257
  });
262
258
  };
263
259
  }
@@ -102,22 +102,28 @@ function cachedTextHeaders(contentType, options = {}) {
102
102
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
103
103
  headers.set("content-type", contentType);
104
104
  headers.set("x-content-type-options", "nosniff");
105
- if (options.etag) headers.set("etag", options.etag);
106
105
  if (options.noindex) headers.set("x-robots-tag", "noindex");
107
106
  return headers;
108
107
  }
109
108
  function createCachedTextResponse(options) {
110
109
  const status = options.status ?? 200;
111
- const method = options.method ?? options.request?.method ?? "GET";
110
+ const method = options.method ?? "GET";
112
111
  const headers = cachedTextHeaders(options.contentType, options);
113
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
114
- return new Response(
115
- method === "HEAD" || isNotModified ? null : options.body,
116
- {
117
- headers,
118
- status: isNotModified ? 304 : status
119
- }
120
- );
112
+ return new Response(method === "HEAD" ? null : options.body, {
113
+ headers,
114
+ status
115
+ });
116
+ }
117
+
118
+ // src/kernel/routes/metadata/canonical-url.ts
119
+ function canonicalUrl(manifest, path) {
120
+ const origin = manifest.site.url?.replace(/\/+$/, "");
121
+ if (!origin) return path;
122
+ try {
123
+ return new URL(path, `${origin}/`).toString();
124
+ } catch {
125
+ return path;
126
+ }
121
127
  }
122
128
 
123
129
  // src/kernel/routes/metadata/llms.ts
@@ -146,10 +152,10 @@ async function llmsTxt(manifest, options = {}) {
146
152
  const visiblePages = options.limit ? pages.slice(0, options.limit) : pages;
147
153
  const lines = visiblePages.map((page) => {
148
154
  const description = options.includeDescriptions && page.description ? ` - ${page.description}` : "";
149
- return `- [${page.title}](${page.path})${description}`;
155
+ return `- [${page.title}](${canonicalUrl(manifest, page.path)})${description}`;
150
156
  });
151
157
  const linkLines = (options.links ?? []).map(
152
- (link) => `- [${link.title}](${link.path})`
158
+ (link) => `- [${link.title}](${canonicalUrl(manifest, link.path)})`
153
159
  );
154
160
  const linkSection = linkLines.length ? `
155
161
 
@@ -175,8 +181,7 @@ function createPrecomputedLlmsTxtRoute(output) {
175
181
  return function GET() {
176
182
  return createCachedTextResponse({
177
183
  body: output.body,
178
- contentType: "text/plain; charset=utf-8",
179
- etag: output.etag
184
+ contentType: "text/plain; charset=utf-8"
180
185
  });
181
186
  };
182
187
  }
@@ -184,8 +189,8 @@ async function llmsFullTxt(manifest, artifacts, options = {}) {
184
189
  const maxBytes = options.maxBytes ?? DEFAULT_LLMS_FULL_MAX_BYTES;
185
190
  const header = `# ${manifest.site.name} Full Content
186
191
 
187
- > LLMS index: /llms.txt
188
- > Sitemap: /sitemap.md
192
+ > LLMS index: ${canonicalUrl(manifest, "/llms.txt")}
193
+ > Sitemap: ${canonicalUrl(manifest, "/sitemap.md")}
189
194
 
190
195
  `;
191
196
  const sections = [];
@@ -17,7 +17,6 @@ type LlmsFullRouteOptions = {
17
17
  };
18
18
  type PrecomputedLlmsTxtOutput = {
19
19
  body: string;
20
- etag?: string;
21
20
  };
22
21
  declare function createLlmsTxtRoute(manifest: ManifestLike, options?: LlmsRouteOptions): () => Promise<Response>;
23
22
  declare function createPrecomputedLlmsTxtRoute(output: PrecomputedLlmsTxtOutput): () => Response;
@@ -17,7 +17,6 @@ type LlmsFullRouteOptions = {
17
17
  };
18
18
  type PrecomputedLlmsTxtOutput = {
19
19
  body: string;
20
- etag?: string;
21
20
  };
22
21
  declare function createLlmsTxtRoute(manifest: ManifestLike, options?: LlmsRouteOptions): () => Promise<Response>;
23
22
  declare function createPrecomputedLlmsTxtRoute(output: PrecomputedLlmsTxtOutput): () => Response;
@@ -75,22 +75,28 @@ function cachedTextHeaders(contentType, options = {}) {
75
75
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
76
76
  headers.set("content-type", contentType);
77
77
  headers.set("x-content-type-options", "nosniff");
78
- if (options.etag) headers.set("etag", options.etag);
79
78
  if (options.noindex) headers.set("x-robots-tag", "noindex");
80
79
  return headers;
81
80
  }
82
81
  function createCachedTextResponse(options) {
83
82
  const status = options.status ?? 200;
84
- const method = options.method ?? options.request?.method ?? "GET";
83
+ const method = options.method ?? "GET";
85
84
  const headers = cachedTextHeaders(options.contentType, options);
86
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
87
- return new Response(
88
- method === "HEAD" || isNotModified ? null : options.body,
89
- {
90
- headers,
91
- status: isNotModified ? 304 : status
92
- }
93
- );
85
+ return new Response(method === "HEAD" ? null : options.body, {
86
+ headers,
87
+ status
88
+ });
89
+ }
90
+
91
+ // src/kernel/routes/metadata/canonical-url.ts
92
+ function canonicalUrl(manifest, path) {
93
+ const origin = manifest.site.url?.replace(/\/+$/, "");
94
+ if (!origin) return path;
95
+ try {
96
+ return new URL(path, `${origin}/`).toString();
97
+ } catch {
98
+ return path;
99
+ }
94
100
  }
95
101
 
96
102
  // src/kernel/routes/metadata/llms.ts
@@ -119,10 +125,10 @@ async function llmsTxt(manifest, options = {}) {
119
125
  const visiblePages = options.limit ? pages.slice(0, options.limit) : pages;
120
126
  const lines = visiblePages.map((page) => {
121
127
  const description = options.includeDescriptions && page.description ? ` - ${page.description}` : "";
122
- return `- [${page.title}](${page.path})${description}`;
128
+ return `- [${page.title}](${canonicalUrl(manifest, page.path)})${description}`;
123
129
  });
124
130
  const linkLines = (options.links ?? []).map(
125
- (link) => `- [${link.title}](${link.path})`
131
+ (link) => `- [${link.title}](${canonicalUrl(manifest, link.path)})`
126
132
  );
127
133
  const linkSection = linkLines.length ? `
128
134
 
@@ -148,8 +154,7 @@ function createPrecomputedLlmsTxtRoute(output) {
148
154
  return function GET() {
149
155
  return createCachedTextResponse({
150
156
  body: output.body,
151
- contentType: "text/plain; charset=utf-8",
152
- etag: output.etag
157
+ contentType: "text/plain; charset=utf-8"
153
158
  });
154
159
  };
155
160
  }
@@ -157,8 +162,8 @@ async function llmsFullTxt(manifest, artifacts, options = {}) {
157
162
  const maxBytes = options.maxBytes ?? DEFAULT_LLMS_FULL_MAX_BYTES;
158
163
  const header = `# ${manifest.site.name} Full Content
159
164
 
160
- > LLMS index: /llms.txt
161
- > Sitemap: /sitemap.md
165
+ > LLMS index: ${canonicalUrl(manifest, "/llms.txt")}
166
+ > Sitemap: ${canonicalUrl(manifest, "/sitemap.md")}
162
167
 
163
168
  `;
164
169
  const sections = [];
@@ -99,22 +99,28 @@ function cachedTextHeaders(contentType, options = {}) {
99
99
  headers.set("vercel-cdn-cache-control", cdnCacheControl(options.cachePolicy));
100
100
  headers.set("content-type", contentType);
101
101
  headers.set("x-content-type-options", "nosniff");
102
- if (options.etag) headers.set("etag", options.etag);
103
102
  if (options.noindex) headers.set("x-robots-tag", "noindex");
104
103
  return headers;
105
104
  }
106
105
  function createCachedTextResponse(options) {
107
106
  const status = options.status ?? 200;
108
- const method = options.method ?? options.request?.method ?? "GET";
107
+ const method = options.method ?? "GET";
109
108
  const headers = cachedTextHeaders(options.contentType, options);
110
- const isNotModified = status === 200 && options.etag && options.request?.headers.get("if-none-match") === options.etag;
111
- return new Response(
112
- method === "HEAD" || isNotModified ? null : options.body,
113
- {
114
- headers,
115
- status: isNotModified ? 304 : status
116
- }
117
- );
109
+ return new Response(method === "HEAD" ? null : options.body, {
110
+ headers,
111
+ status
112
+ });
113
+ }
114
+
115
+ // src/kernel/routes/metadata/canonical-url.ts
116
+ function canonicalUrl(manifest, path) {
117
+ const origin = manifest.site.url?.replace(/\/+$/, "");
118
+ if (!origin) return path;
119
+ try {
120
+ return new URL(path, `${origin}/`).toString();
121
+ } catch {
122
+ return path;
123
+ }
118
124
  }
119
125
 
120
126
  // src/kernel/routes/metadata/robots.ts
@@ -122,7 +128,11 @@ async function robotsTxt(manifest) {
122
128
  "use cache";
123
129
  (0, import_cache.cacheLife)("page");
124
130
  (0, import_cache.cacheTag)(tags.site(), tags.robots(), tags.manifest(manifest.contentHash));
125
- return "User-agent: *\nAllow: /\nSitemap: /sitemap.xml\n";
131
+ const sitemapUrl = canonicalUrl(manifest, "/sitemap.xml");
132
+ return `User-agent: *
133
+ Allow: /
134
+ Sitemap: ${sitemapUrl}
135
+ `;
126
136
  }
127
137
  function createRobotsTxtRoute(manifest) {
128
138
  return async function GET() {