@ampless/runtime 0.2.0-alpha.8 → 1.0.0-alpha.13

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/index.js CHANGED
@@ -20,35 +20,20 @@ function createStorage(outputs) {
20
20
  // src/posts.ts
21
21
  import { cookies } from "next/headers";
22
22
  import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
23
- function decodeBody(value) {
24
- if (typeof value !== "string") return value;
25
- try {
26
- return JSON.parse(value);
27
- } catch {
28
- return value;
29
- }
30
- }
23
+ import { decodeAwsJson } from "ampless";
31
24
  function decodeMetadata(value) {
32
25
  if (value === null || value === void 0) return void 0;
33
- const parsed = typeof value === "string" ? safeJsonParse(value) : value;
26
+ const parsed = decodeAwsJson(value);
34
27
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
35
28
  }
36
- function safeJsonParse(value) {
37
- try {
38
- return JSON.parse(value);
39
- } catch {
40
- return value;
41
- }
42
- }
43
29
  function toCorePost(p) {
44
30
  return {
45
31
  postId: p.postId,
46
- siteId: p.siteId,
47
32
  slug: p.slug,
48
33
  title: p.title,
49
34
  excerpt: p.excerpt ?? void 0,
50
35
  format: p.format ?? "markdown",
51
- body: decodeBody(p.body),
36
+ body: decodeAwsJson(p.body),
52
37
  status: p.status ?? "draft",
53
38
  publishedAt: p.publishedAt ?? void 0,
54
39
  tags: (p.tags ?? []).filter((t) => typeof t === "string"),
@@ -66,7 +51,6 @@ function createPostsApi(outputs) {
66
51
  return {
67
52
  async listPublishedPosts(opts = {}) {
68
53
  const { data, errors } = await client.queries.listPublishedPosts({
69
- siteId: opts.siteId ?? "default",
70
54
  from: opts.from,
71
55
  to: opts.to,
72
56
  limit: opts.limit ?? 20,
@@ -76,9 +60,8 @@ function createPostsApi(outputs) {
76
60
  const items = (data?.items ?? []).filter((p) => p !== null).map(toCorePost);
77
61
  return { items, nextToken: data?.nextToken ?? null };
78
62
  },
79
- async getPublishedPost(slug, opts = {}) {
63
+ async getPublishedPost(slug) {
80
64
  const { data, errors } = await client.queries.getPublishedPost({
81
- siteId: opts.siteId ?? "default",
82
65
  slug
83
66
  });
84
67
  if (errors) throw new Error(errors[0]?.message ?? "Failed to get post");
@@ -86,7 +69,6 @@ function createPostsApi(outputs) {
86
69
  },
87
70
  async listPostsByTag(tag, opts = {}) {
88
71
  const { data, errors } = await client.queries.listPostsByTag({
89
- siteId: opts.siteId ?? "default",
90
72
  tag,
91
73
  limit: opts.limit ?? 20,
92
74
  nextToken: opts.nextToken
@@ -99,25 +81,27 @@ function createPostsApi(outputs) {
99
81
  }
100
82
 
101
83
  // src/site-settings.ts
102
- import { DEFAULT_SITE_ID, siteFor, unflattenSettings } from "ampless";
84
+ import { unflattenSettings } from "ampless";
103
85
  function createSiteSettings(cmsConfig, storage) {
104
- async function fetchRemote(siteId) {
86
+ async function fetchRemote() {
105
87
  if (!storage.isStorageConfigured()) return null;
106
88
  let url;
107
89
  try {
108
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
90
+ url = storage.publicAssetUrl("public/site-settings.json");
109
91
  } catch {
110
92
  return null;
111
93
  }
112
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
94
+ const res = await fetch(url, {
95
+ next: { revalidate: 60, tags: ["site-settings"] }
96
+ });
113
97
  if (!res.ok) return null;
114
98
  const flat = await res.json();
115
99
  return unflattenSettings(flat);
116
100
  }
117
101
  return {
118
- async loadSiteSettings(siteId = DEFAULT_SITE_ID) {
119
- const remote = await fetchRemote(siteId).catch(() => null);
120
- const baseSite = siteFor(siteId, cmsConfig);
102
+ async loadSiteSettings() {
103
+ const remote = await fetchRemote().catch(() => null);
104
+ const baseSite = cmsConfig.site;
121
105
  return {
122
106
  site: {
123
107
  name: remote?.site?.name ?? baseSite.name,
@@ -142,15 +126,14 @@ function createSiteSettings(cmsConfig, storage) {
142
126
  }
143
127
 
144
128
  // src/seo.ts
145
- import { DEFAULT_SITE_ID as DEFAULT_SITE_ID2 } from "ampless";
146
129
  function isPlugin(p) {
147
130
  return typeof p === "object" && p !== null && "apiVersion" in p;
148
131
  }
149
132
  function createSeo(cmsConfig, settingsApi) {
150
133
  const plugins = (cmsConfig.plugins ?? []).filter(isPlugin);
151
134
  return {
152
- async postMetadata(post, siteId = DEFAULT_SITE_ID2) {
153
- const settings = await settingsApi.loadSiteSettings(siteId);
135
+ async postMetadata(post) {
136
+ const settings = await settingsApi.loadSiteSettings();
154
137
  const accum = {};
155
138
  for (const plugin of plugins) {
156
139
  if (!plugin.metadata) continue;
@@ -167,8 +150,8 @@ function createSeo(cmsConfig, settingsApi) {
167
150
  }
168
151
  return accum;
169
152
  },
170
- async siteMetadata(siteId = DEFAULT_SITE_ID2) {
171
- const settings = await settingsApi.loadSiteSettings(siteId);
153
+ async siteMetadata() {
154
+ const settings = await settingsApi.loadSiteSettings();
172
155
  const accum = {
173
156
  title: settings.site.name,
174
157
  description: settings.site.description
@@ -193,24 +176,25 @@ function createSeo(cmsConfig, settingsApi) {
193
176
 
194
177
  // src/theme-active.ts
195
178
  import { headers } from "next/headers";
196
- import { DEFAULT_SITE_ID as DEFAULT_SITE_ID3 } from "ampless";
197
179
  function createThemeActive(registry, storage) {
198
- async function fetchActiveFromCache(siteId) {
180
+ async function fetchActiveFromCache() {
199
181
  if (!storage.isStorageConfigured()) return null;
200
182
  let url;
201
183
  try {
202
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
184
+ url = storage.publicAssetUrl("public/site-settings.json");
203
185
  } catch {
204
186
  return null;
205
187
  }
206
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
188
+ const res = await fetch(url, {
189
+ next: { revalidate: 60, tags: ["site-settings"] }
190
+ });
207
191
  if (!res.ok) return null;
208
192
  const flat = await res.json();
209
193
  const v = flat["theme.active"];
210
194
  return typeof v === "string" ? v : null;
211
195
  }
212
196
  return {
213
- async resolveActiveTheme(siteId = DEFAULT_SITE_ID3) {
197
+ async resolveActiveTheme() {
214
198
  let previewOverride = null;
215
199
  try {
216
200
  const h = await headers();
@@ -221,7 +205,7 @@ function createThemeActive(registry, storage) {
221
205
  const mod2 = registry.themes[previewOverride];
222
206
  if (mod2) return { name: previewOverride, module: mod2 };
223
207
  }
224
- const stored = await fetchActiveFromCache(siteId).catch(() => null);
208
+ const stored = await fetchActiveFromCache().catch(() => null);
225
209
  const name = stored && stored in registry.themes ? stored : registry.defaultTheme;
226
210
  const mod = registry.themes[name] ?? registry.themes[registry.defaultTheme];
227
211
  if (!mod) {
@@ -236,7 +220,6 @@ function createThemeActive(registry, storage) {
236
220
 
237
221
  // src/theme-config.ts
238
222
  import {
239
- DEFAULT_SITE_ID as DEFAULT_SITE_ID4,
240
223
  resolveThemeValues,
241
224
  themeSettingKey
242
225
  } from "ampless";
@@ -247,23 +230,23 @@ function validateColorScheme(raw) {
247
230
  return DEFAULT_COLOR_SCHEME;
248
231
  }
249
232
  function createThemeConfig(themeActive, storage) {
250
- async function fetchRemote(siteId) {
233
+ async function fetchRemote() {
251
234
  if (!storage.isStorageConfigured()) return null;
252
235
  let url;
253
236
  try {
254
- url = storage.publicAssetUrl(`public/site-settings/${siteId}.json`);
237
+ url = storage.publicAssetUrl("public/site-settings.json");
255
238
  } catch {
256
239
  return null;
257
240
  }
258
- const res = await fetch(url, { next: { revalidate: 60, tags: [`site-settings:${siteId}`] } });
241
+ const res = await fetch(url, { next: { revalidate: 60, tags: ["site-settings"] } });
259
242
  if (!res.ok) return null;
260
243
  return await res.json();
261
244
  }
262
245
  return {
263
- async loadThemeConfig(siteId = DEFAULT_SITE_ID4) {
246
+ async loadThemeConfig() {
264
247
  const [active, flat] = await Promise.all([
265
- themeActive.resolveActiveTheme(siteId),
266
- fetchRemote(siteId).catch(() => null)
248
+ themeActive.resolveActiveTheme(),
249
+ fetchRemote().catch(() => null)
267
250
  ]);
268
251
  const manifest = active.module.manifest;
269
252
  const stored = {};
@@ -299,9 +282,17 @@ ${lines.join("\n")}
299
282
  }
300
283
 
301
284
  // src/rendering.ts
285
+ import { marked } from "marked";
302
286
  function escape(s) {
303
287
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
304
288
  }
289
+ function textAlignStyle(attrs) {
290
+ const v = attrs?.textAlign;
291
+ if (v === "left" || v === "center" || v === "right" || v === "justify") {
292
+ return ` style="text-align: ${v}"`;
293
+ }
294
+ return "";
295
+ }
305
296
  function renderTiptap(node) {
306
297
  if (node.type === "text") {
307
298
  let html = escape(node.text ?? "");
@@ -310,6 +301,8 @@ function renderTiptap(node) {
310
301
  else if (mark.type === "italic") html = `<em>${html}</em>`;
311
302
  else if (mark.type === "code") html = `<code>${html}</code>`;
312
303
  else if (mark.type === "strike") html = `<s>${html}</s>`;
304
+ else if (mark.type === "underline") html = `<u>${html}</u>`;
305
+ else if (mark.type === "highlight") html = `<mark>${html}</mark>`;
313
306
  else if (mark.type === "link") {
314
307
  const href = escape(String(mark.attrs?.href ?? "#"));
315
308
  html = `<a href="${href}" target="_blank" rel="noopener">${html}</a>`;
@@ -322,10 +315,10 @@ function renderTiptap(node) {
322
315
  case "doc":
323
316
  return children;
324
317
  case "paragraph":
325
- return `<p>${children}</p>`;
318
+ return `<p${textAlignStyle(node.attrs)}>${children}</p>`;
326
319
  case "heading": {
327
320
  const level = Number(node.attrs?.level ?? 1);
328
- return `<h${level}>${children}</h${level}>`;
321
+ return `<h${level}${textAlignStyle(node.attrs)}>${children}</h${level}>`;
329
322
  }
330
323
  case "bulletList":
331
324
  return `<ul>${children}</ul>`;
@@ -350,52 +343,39 @@ function renderTiptap(node) {
350
343
  const display = node.attrs?.display ? ` data-display="${escape(String(node.attrs.display))}"` : "";
351
344
  return `<img src="${src}" alt="${alt}"${title}${display} loading="lazy" />`;
352
345
  }
346
+ case "table":
347
+ return `<table class="tiptap-table"><tbody>${children}</tbody></table>`;
348
+ case "tableRow":
349
+ return `<tr>${children}</tr>`;
350
+ case "tableHeader":
351
+ return `<th${tableCellAttrs(node.attrs)}>${children}</th>`;
352
+ case "tableCell":
353
+ return `<td${tableCellAttrs(node.attrs)}>${children}</td>`;
354
+ case "taskList":
355
+ return `<ul data-type="taskList">${children}</ul>`;
356
+ case "taskItem": {
357
+ const checked = node.attrs?.checked === true ? "true" : "false";
358
+ return `<li data-type="taskItem" data-checked="${checked}">${children}</li>`;
359
+ }
353
360
  default:
354
361
  return children;
355
362
  }
356
363
  }
357
- function renderMarkdown(md) {
358
- const lines = md.split("\n");
359
- const out = [];
360
- let i = 0;
361
- while (i < lines.length) {
362
- const line = lines[i] ?? "";
363
- if (line.startsWith("# ")) {
364
- out.push(`<h1>${escape(line.slice(2))}</h1>`);
365
- i++;
366
- } else if (line.startsWith("## ")) {
367
- out.push(`<h2>${escape(line.slice(3))}</h2>`);
368
- i++;
369
- } else if (line.startsWith("```")) {
370
- const lang = line.slice(3).trim();
371
- const code = [];
372
- i++;
373
- while (i < lines.length && !(lines[i] ?? "").startsWith("```")) {
374
- code.push(lines[i] ?? "");
375
- i++;
376
- }
377
- i++;
378
- out.push(
379
- `<pre><code${lang ? ` class="language-${escape(lang)}"` : ""}>${escape(code.join("\n"))}</code></pre>`
380
- );
381
- } else if (line.startsWith("- ")) {
382
- const items = [];
383
- while (i < lines.length && (lines[i] ?? "").startsWith("- ")) {
384
- items.push(`<li>${renderInlineMarkdown((lines[i] ?? "").slice(2))}</li>`);
385
- i++;
386
- }
387
- out.push(`<ul>${items.join("")}</ul>`);
388
- } else if (line.trim() === "") {
389
- i++;
390
- } else {
391
- out.push(`<p>${renderInlineMarkdown(line)}</p>`);
392
- i++;
393
- }
364
+ function tableCellAttrs(attrs) {
365
+ let out = "";
366
+ const colspan = Number(attrs?.colspan ?? 1);
367
+ if (colspan > 1) out += ` colspan="${colspan}"`;
368
+ const rowspan = Number(attrs?.rowspan ?? 1);
369
+ if (rowspan > 1) out += ` rowspan="${rowspan}"`;
370
+ const colwidth = attrs?.colwidth;
371
+ if (Array.isArray(colwidth) && colwidth.length > 0) {
372
+ const w = Number(colwidth[0]);
373
+ if (Number.isFinite(w) && w > 0) out += ` style="width: ${w}px"`;
394
374
  }
395
- return out.join("\n");
375
+ return out;
396
376
  }
397
- function renderInlineMarkdown(text) {
398
- return text.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
377
+ function renderMarkdown(md) {
378
+ return marked.parse(md, { gfm: true, breaks: false, async: false });
399
379
  }
400
380
  function renderBody(post) {
401
381
  if (post.format === "html") return String(post.body);
@@ -426,6 +406,8 @@ function tiptapNodeToMarkdown(node) {
426
406
  else if (mark.type === "italic") txt = `*${txt}*`;
427
407
  else if (mark.type === "code") txt = `\`${txt}\``;
428
408
  else if (mark.type === "strike") txt = `~~${txt}~~`;
409
+ else if (mark.type === "underline") txt = `<u>${txt}</u>`;
410
+ else if (mark.type === "highlight") txt = `<mark>${txt}</mark>`;
429
411
  else if (mark.type === "link") txt = `[${txt}](${String(mark.attrs?.href ?? "#")})`;
430
412
  }
431
413
  return txt;
@@ -463,12 +445,54 @@ function tiptapNodeToMarkdown(node) {
463
445
  const alt = String(node.attrs?.alt ?? "");
464
446
  return `![${alt}](${src})`;
465
447
  }
448
+ case "table":
449
+ return tiptapTableToMarkdown(node);
450
+ case "taskList":
451
+ return children + "\n";
452
+ case "taskItem": {
453
+ const checked = node.attrs?.checked === true ? "x" : " ";
454
+ const inner = children.replace(/\n+$/, "");
455
+ const [first, ...rest] = inner.split("\n");
456
+ const cont = rest.map((l) => l ? " " + l : l).join("\n");
457
+ return `- [${checked}] ${first ?? ""}${cont ? "\n" + cont : ""}
458
+ `;
459
+ }
466
460
  default:
467
461
  return children;
468
462
  }
469
463
  }
464
+ function tiptapTableToMarkdown(node) {
465
+ const rows = node.content ?? [];
466
+ if (rows.length === 0) return "";
467
+ const renderedRows = [];
468
+ let headerIdx = -1;
469
+ for (let i = 0; i < rows.length; i++) {
470
+ const row = rows[i];
471
+ const cells = row.content ?? [];
472
+ const cellTexts = cells.map((c) => {
473
+ const inner = (c.content ?? []).map(tiptapNodeToMarkdown).join("");
474
+ return inner.replace(/\n+$/, "").replace(/\n/g, "<br>").replace(/\|/g, "\\|");
475
+ });
476
+ renderedRows.push(cellTexts);
477
+ if (headerIdx === -1 && cells.some((c) => c.type === "tableHeader")) headerIdx = i;
478
+ }
479
+ if (headerIdx === -1) headerIdx = 0;
480
+ const header = renderedRows[headerIdx] ?? [];
481
+ const body = renderedRows.filter((_, i) => i !== headerIdx);
482
+ const cols = header.length;
483
+ const headerLine = "| " + header.join(" | ") + " |";
484
+ const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
485
+ const bodyLines = body.map((r) => {
486
+ const cells = Array.from({ length: cols }, (_, i) => r[i] ?? "");
487
+ return "| " + cells.join(" | ") + " |";
488
+ });
489
+ return "\n" + [headerLine, sepLine, ...bodyLines].join("\n") + "\n\n";
490
+ }
470
491
  function htmlToMarkdown(html) {
471
492
  let md = html;
493
+ md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, inner) => {
494
+ return "\n" + convertHtmlTable(String(inner)) + "\n";
495
+ });
472
496
  md = md.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, text) => {
473
497
  return "\n" + "#".repeat(Number(level)) + " " + String(text).trim() + "\n\n";
474
498
  });
@@ -481,6 +505,9 @@ function htmlToMarkdown(html) {
481
505
  md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
482
506
  return "\n" + String(content).trim().split("\n").map((l) => "> " + l).join("\n") + "\n\n";
483
507
  });
508
+ md = md.replace(/<ul[^>]*data-type="taskList"[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
509
+ return "\n" + convertHtmlTaskList(String(items)) + "\n";
510
+ });
484
511
  md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
485
512
  return "\n" + String(items).replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n") + "\n";
486
513
  });
@@ -500,11 +527,63 @@ function htmlToMarkdown(html) {
500
527
  md = md.replace(/<(em|i)>([\s\S]*?)<\/\1>/gi, "*$2*");
501
528
  md = md.replace(/<s>([\s\S]*?)<\/s>/gi, "~~$1~~");
502
529
  md = md.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`");
530
+ const PH_U_OPEN = "AMP_U_OPEN";
531
+ const PH_U_CLOSE = "AMP_U_CLOSE";
532
+ const PH_MARK_OPEN = "AMP_MARK_OPEN";
533
+ const PH_MARK_CLOSE = "AMP_MARK_CLOSE";
534
+ md = md.replace(/<u>([\s\S]*?)<\/u>/gi, `${PH_U_OPEN}$1${PH_U_CLOSE}`);
535
+ md = md.replace(/<mark>([\s\S]*?)<\/mark>/gi, `${PH_MARK_OPEN}$1${PH_MARK_CLOSE}`);
503
536
  md = md.replace(/<\/?[^>]+>/g, "");
537
+ md = md.split(PH_U_OPEN).join("<u>").split(PH_U_CLOSE).join("</u>").split(PH_MARK_OPEN).join("<mark>").split(PH_MARK_CLOSE).join("</mark>");
504
538
  md = md.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
505
539
  md = md.replace(/\n{3,}/g, "\n\n");
506
540
  return md.trim() + "\n";
507
541
  }
542
+ function convertHtmlTable(inner) {
543
+ const stripped = inner.replace(/<\/?(thead|tbody)[^>]*>/gi, "");
544
+ const rowRe = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
545
+ const rows = [];
546
+ let m;
547
+ while ((m = rowRe.exec(stripped)) !== null) {
548
+ const rowHtml = m[1] ?? "";
549
+ const cellRe = /<(th|td)[^>]*>([\s\S]*?)<\/\1>/gi;
550
+ const cells = [];
551
+ let isHeader = false;
552
+ let cm;
553
+ while ((cm = cellRe.exec(rowHtml)) !== null) {
554
+ if ((cm[1] ?? "").toLowerCase() === "th") isHeader = true;
555
+ cells.push(normalizeTableCell(cm[2] ?? ""));
556
+ }
557
+ if (cells.length > 0) rows.push({ isHeader, cells });
558
+ }
559
+ if (rows.length === 0) return "";
560
+ let headerIdx = rows.findIndex((r) => r.isHeader);
561
+ if (headerIdx === -1) headerIdx = 0;
562
+ const header = rows[headerIdx].cells;
563
+ const body = rows.filter((_, i) => i !== headerIdx).map((r) => r.cells);
564
+ const cols = header.length;
565
+ const headerLine = "| " + header.join(" | ") + " |";
566
+ const sepLine = "| " + Array.from({ length: cols }, () => "---").join(" | ") + " |";
567
+ const bodyLines = body.map((cells) => {
568
+ const padded = Array.from({ length: cols }, (_, i) => cells[i] ?? "");
569
+ return "| " + padded.join(" | ") + " |";
570
+ });
571
+ return [headerLine, sepLine, ...bodyLines].join("\n") + "\n";
572
+ }
573
+ function normalizeTableCell(html) {
574
+ return html.replace(/<br\s*\/?>/gi, " ").replace(/<\/?[^>]+>/g, "").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
575
+ }
576
+ function convertHtmlTaskList(items) {
577
+ const liRe = /<li[^>]*data-checked="(true|false)"[^>]*>([\s\S]*?)<\/li>/gi;
578
+ const out = [];
579
+ let m;
580
+ while ((m = liRe.exec(items)) !== null) {
581
+ const checked = m[1] === "true" ? "x" : " ";
582
+ const inner = String(m[2] ?? "").replace(/<\/?p[^>]*>/gi, "").trim();
583
+ out.push(`- [${checked}] ${inner}`);
584
+ }
585
+ return out.join("\n");
586
+ }
508
587
 
509
588
  // src/index.ts
510
589
  function createAmpless(opts) {
@@ -517,13 +596,13 @@ function createAmpless(opts) {
517
596
  const themeConfig = createThemeConfig(themeActive, storage);
518
597
  return {
519
598
  listPublishedPosts: (o) => posts.listPublishedPosts(o),
520
- getPublishedPost: (slug, o) => posts.getPublishedPost(slug, o),
599
+ getPublishedPost: (slug) => posts.getPublishedPost(slug),
521
600
  listPostsByTag: (tag, o) => posts.listPostsByTag(tag, o),
522
- loadSiteSettings: (siteId) => settings.loadSiteSettings(siteId),
523
- resolveActiveTheme: (siteId) => themeActive.resolveActiveTheme(siteId),
524
- loadThemeConfig: (siteId) => themeConfig.loadThemeConfig(siteId),
525
- postMetadata: (post, siteId) => seo.postMetadata(post, siteId),
526
- siteMetadata: (siteId) => seo.siteMetadata(siteId),
601
+ loadSiteSettings: () => settings.loadSiteSettings(),
602
+ resolveActiveTheme: () => themeActive.resolveActiveTheme(),
603
+ loadThemeConfig: () => themeConfig.loadThemeConfig(),
604
+ postMetadata: (post) => seo.postMetadata(post),
605
+ siteMetadata: () => seo.siteMetadata(),
527
606
  renderBody: (post) => renderBody(post),
528
607
  renderThemeCss: (cssVars) => renderThemeCss(cssVars),
529
608
  publicAssetUrl: (key) => storage.publicAssetUrl(key),
@@ -8,24 +8,26 @@ type MiddlewareFn = (request: NextRequest) => NextResponse;
8
8
  /**
9
9
  * Build the ampless public-site middleware. Performs:
10
10
  *
11
- * - hostnamesiteId resolution (multi-site rewrites)
12
- * - `/path` → `/site/<siteId>/path` internal rewrite
11
+ * - `/path``/site/default/path` internal rewrite
12
+ * - `/_/<slug>(/...)` → `/site/default/r/<slug>(/...)` rewrite for
13
+ * the unified bare-HTML / static-bundle route
13
14
  * - `?previewTheme=<name>` → `x-preview-theme` header forwarding
14
- * - `Cache-Control: private, no-store` in multi-site mode (Amplify
15
- * Hosting's CloudFront cache key doesn't include Host, so SSR
16
- * responses would cross-contaminate at the same path)
17
15
  *
18
- * No-layout / bare-HTML routing is data-driven: the theme post
19
- * dispatcher reads `post.metadata.no_layout` and redirects to
20
- * `/raw/<slug>` when set. The plain prefix-prepend below picks up
21
- * that path because the raw route lives at
22
- * `app/site/<siteId>/raw/<slug>/route.ts` no special middleware
23
- * branch is needed.
16
+ * Bare-HTML / static routing is data-driven: the theme post dispatcher
17
+ * redirects `metadata.no_layout` and `format='static'` posts to
18
+ * `/_/<slug>`. The middleware rewrites the public `_` prefix to the
19
+ * routable internal folder name `r/`, because Next.js's App Router
20
+ * excludes any path part starting with `_` during route discovery
21
+ * (see `recursive-readdir` + `ignorePartFilter` in
22
+ * `next/dist/build/route-discovery.js`). The browser URL stays
23
+ * `/_/<slug>(/...)`; only the rewrite target uses `r/`.
24
24
  *
25
- * The factory captures the multi-site flag at construction time so the
26
- * hot path stays a pair of cheap header lookups.
25
+ * Single site per Amplify deployment: the rewritten internal path uses
26
+ * the fixed `default` segment. The internal `/site/[siteId]/` routing
27
+ * tree is retained as an implementation detail; a follow-up PR
28
+ * flattens the URL structure further.
27
29
  */
28
- declare function createAmplessMiddleware({ cmsConfig }: CreateMiddlewareOpts): MiddlewareFn;
30
+ declare function createAmplessMiddleware(_opts: CreateMiddlewareOpts): MiddlewareFn;
29
31
  /**
30
32
  * Reference matcher config — admin / api / login / static assets /
31
33
  * amplify_outputs.json are excluded so middleware doesn't rewrite
@@ -1,31 +1,22 @@
1
1
  // src/middleware.ts
2
2
  import { NextResponse } from "next/server";
3
- import { resolveSiteId, isMultiSite } from "ampless";
4
- function createAmplessMiddleware({ cmsConfig }) {
5
- const MULTI_SITE = isMultiSite(cmsConfig);
3
+ var INTERNAL_SITE_SEGMENT = "default";
4
+ function createAmplessMiddleware(_opts) {
6
5
  return function middleware(request) {
7
- const host = (request.headers.get("host") ?? "").split(":")[0];
8
- const siteId = resolveSiteId(host ?? "", cmsConfig);
9
- if (!siteId) {
10
- return new NextResponse("Site not found", { status: 404 });
11
- }
12
6
  const url = request.nextUrl.clone();
13
7
  if (!url.pathname.startsWith("/site/")) {
14
- const tail = url.pathname === "/" ? "" : url.pathname;
15
- url.pathname = `/site/${siteId}${tail}`;
8
+ let tail = url.pathname === "/" ? "" : url.pathname;
9
+ if (tail === "/_" || tail.startsWith("/_/")) {
10
+ tail = `/r${tail.slice(2)}`;
11
+ }
12
+ url.pathname = `/site/${INTERNAL_SITE_SEGMENT}${tail}`;
16
13
  }
17
14
  const previewTheme = url.searchParams.get("previewTheme");
18
15
  const previewColorScheme = url.searchParams.get("previewColorScheme");
19
16
  const requestHeaders = new Headers(request.headers);
20
- requestHeaders.set("x-site-id", siteId);
21
17
  if (previewTheme) requestHeaders.set("x-preview-theme", previewTheme);
22
18
  if (previewColorScheme) requestHeaders.set("x-preview-color-scheme", previewColorScheme);
23
- const response = NextResponse.rewrite(url, { request: { headers: requestHeaders } });
24
- response.headers.set("x-site-id", siteId);
25
- if (MULTI_SITE) {
26
- response.headers.set("Cache-Control", "private, no-store");
27
- }
28
- return response;
19
+ return NextResponse.rewrite(url, { request: { headers: requestHeaders } });
29
20
  };
30
21
  }
31
22
  var defaultMatcherConfig = {