@ampless/runtime 1.0.0-beta.60 → 1.0.0-beta.62

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.
@@ -0,0 +1,10 @@
1
+ // src/request-headers.ts
2
+ var AMPLESS_PATHNAME_HEADER = "x-ampless-pathname";
3
+ var PREVIEW_THEME_HEADER = "x-preview-theme";
4
+ var PREVIEW_COLOR_SCHEME_HEADER = "x-preview-color-scheme";
5
+
6
+ export {
7
+ AMPLESS_PATHNAME_HEADER,
8
+ PREVIEW_THEME_HEADER,
9
+ PREVIEW_COLOR_SCHEME_HEADER
10
+ };
package/dist/index.d.ts CHANGED
@@ -336,6 +336,20 @@ interface PluginHeadApi {
336
336
  * `pluginSettings.loadAll()`.
337
337
  */
338
338
  contextForPlugins(): Promise<(plugin: AmplessPlugin) => PluginPublicRenderContext>;
339
+ /**
340
+ * Non-gated variant of `renderHead()` for use by the admin post preview.
341
+ *
342
+ * WHY this bypasses `isPublicRequest()`: the preview iframe is editor-only
343
+ * and must faithfully show what the published article looks like — including
344
+ * content-decoration plugins like mermaid/highlight that only register their
345
+ * scripts via `publicHead`. The `isPublicRequest()` gate correctly blocks
346
+ * analytics on public pages served to the admin or login routes, but for the
347
+ * preview we intentionally collect ALL publicHead descriptors so diagrams and
348
+ * syntax highlighting render. Analytics scripts (GA4/GTM/Plausible) also fire
349
+ * on preview as a side-effect; this is accepted (preview is not end-user
350
+ * traffic). Do NOT use this from public layouts — use `renderHead()` there.
351
+ */
352
+ renderHeadForPreview(): Promise<ReactNode>;
339
353
  }
340
354
  /**
341
355
  * Position-bucketed result of `renderHtmlForPost`. Themes embed these
@@ -367,18 +381,6 @@ interface PublicHtmlForPostResult {
367
381
  * U+2029 → '\u2029'
368
382
  */
369
383
  declare function escapeJsonLdInlineBody(value: string): string;
370
- /**
371
- * Create the head/body renderer for a `Config`. The constructor-time
372
- * pass logs a single dev warning when two plugins share an
373
- * `instanceId ?? name`; everything else happens at render time so
374
- * descriptors reflect per-request site config.
375
- *
376
- * `pluginSettings` (Phase 2) is the runtime accessor that pulls
377
- * admin-managed `settings.public` values from the S3 site-settings
378
- * cache. Within a single request we fetch once via `loadAll()` and
379
- * bind a per-plugin `ctx.setting(key)` accessor before invoking
380
- * either `publicHead` or `publicBodyEnd`.
381
- */
382
384
  declare function createPluginHead(cmsConfig: Config, pluginSettings: PluginSettingsApi): PluginHeadApi;
383
385
 
384
386
  interface ThemesRegistry {
@@ -588,6 +590,13 @@ interface Ampless {
588
590
  postMetadata(post: Post): Promise<Metadata>;
589
591
  siteMetadata(): Promise<Metadata>;
590
592
  publicHead(): Promise<ReactNode>;
593
+ /**
594
+ * Non-gated head collector for the admin post preview. Collects ALL
595
+ * `publicHead` descriptors without calling `isPublicRequest()` so
596
+ * content-decoration plugins (mermaid, highlight) render in preview.
597
+ * Do NOT call from public layouts — use `publicHead()` there.
598
+ */
599
+ publicHeadForPreview(): Promise<ReactNode>;
591
600
  publicBodyEnd(): Promise<ReactNode>;
592
601
  /**
593
602
  * Per-post body descriptors (Phase 4 `schema` capability). Theme
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ AMPLESS_PATHNAME_HEADER,
3
+ PREVIEW_COLOR_SCHEME_HEADER,
4
+ PREVIEW_THEME_HEADER
5
+ } from "./chunk-BS7KLDDE.js";
1
6
  import {
2
7
  _resetStreamS3Cache,
3
8
  streamS3Object,
@@ -238,6 +243,7 @@ function createSeo(cmsConfig, settingsApi) {
238
243
  }
239
244
 
240
245
  // src/plugin-head.ts
246
+ import { headers } from "next/headers";
241
247
  import {
242
248
  Fragment as Fragment2,
243
249
  createElement as createElement2
@@ -1428,6 +1434,20 @@ function collectHtmlForPost(plugins, site, snapshot, post) {
1428
1434
  afterContent: toReactNode(after, "afterContent")
1429
1435
  };
1430
1436
  }
1437
+ async function isPublicRequest() {
1438
+ let h;
1439
+ try {
1440
+ h = await headers();
1441
+ } catch {
1442
+ return false;
1443
+ }
1444
+ const p = h.get(AMPLESS_PATHNAME_HEADER);
1445
+ if (!p) return false;
1446
+ if (p === "/admin" || p.startsWith("/admin/")) return false;
1447
+ if (p === "/login" || p.startsWith("/login/")) return false;
1448
+ if (h.get(PREVIEW_THEME_HEADER) || h.get(PREVIEW_COLOR_SCHEME_HEADER)) return false;
1449
+ return true;
1450
+ }
1431
1451
  function createPluginHead(cmsConfig, pluginSettings) {
1432
1452
  const plugins = (cmsConfig.plugins ?? []).filter(isPlugin2);
1433
1453
  const validPlugins = [];
@@ -1532,6 +1552,7 @@ function createPluginHead(cmsConfig, pluginSettings) {
1532
1552
  }
1533
1553
  return {
1534
1554
  async renderHead() {
1555
+ if (!await isPublicRequest()) return null;
1535
1556
  const snapshot = await pluginSettings.loadAll();
1536
1557
  return collectFor(
1537
1558
  validPlugins,
@@ -1542,6 +1563,7 @@ function createPluginHead(cmsConfig, pluginSettings) {
1542
1563
  );
1543
1564
  },
1544
1565
  async renderBodyEnd() {
1566
+ if (!await isPublicRequest()) return null;
1545
1567
  const snapshot = await pluginSettings.loadAll();
1546
1568
  return collectFor(
1547
1569
  validPlugins,
@@ -1567,6 +1589,16 @@ function createPluginHead(cmsConfig, pluginSettings) {
1567
1589
  async contextForPlugins() {
1568
1590
  const snapshot = await pluginSettings.loadAll();
1569
1591
  return (plugin) => makeCtx(plugin, cmsConfig.site, snapshot);
1592
+ },
1593
+ async renderHeadForPreview() {
1594
+ const snapshot = await pluginSettings.loadAll();
1595
+ return collectFor(
1596
+ validPlugins,
1597
+ cmsConfig.site,
1598
+ snapshot,
1599
+ (p) => p.publicHead,
1600
+ renderHeadDescriptor
1601
+ );
1570
1602
  }
1571
1603
  };
1572
1604
  }
@@ -1664,7 +1696,7 @@ function createPluginSettings(storage) {
1664
1696
  }
1665
1697
 
1666
1698
  // src/theme-active.ts
1667
- import { headers } from "next/headers";
1699
+ import { headers as headers2 } from "next/headers";
1668
1700
  function createThemeActive(registry, storage) {
1669
1701
  function settingsUrl() {
1670
1702
  if (!storage.isStorageConfigured()) return null;
@@ -1699,8 +1731,8 @@ function createThemeActive(registry, storage) {
1699
1731
  async resolveActiveTheme() {
1700
1732
  let previewOverride = null;
1701
1733
  try {
1702
- const h = await headers();
1703
- previewOverride = h.get("x-preview-theme");
1734
+ const h = await headers2();
1735
+ previewOverride = h.get(PREVIEW_THEME_HEADER);
1704
1736
  } catch {
1705
1737
  }
1706
1738
  if (previewOverride && previewOverride in registry.themes) {
@@ -1807,6 +1839,7 @@ function createAmpless(opts) {
1807
1839
  postMetadata: (post) => seo.postMetadata(post),
1808
1840
  siteMetadata: () => seo.siteMetadata(),
1809
1841
  publicHead: () => pluginHead.renderHead(),
1842
+ publicHeadForPreview: () => pluginHead.renderHeadForPreview(),
1810
1843
  publicBodyEnd: () => pluginHead.renderBodyEnd(),
1811
1844
  publicBodyForPost: (post) => pluginHead.renderBodyForPost(post),
1812
1845
  publicHtmlForPost: (post) => pluginHead.renderHtmlForPost(post),
@@ -37,6 +37,12 @@ declare function computeCacheControl(flags: {
37
37
  * - `?previewTheme` / `?previewColorScheme` → `x-preview-theme` /
38
38
  * `x-preview-color-scheme` header forwarding for the admin's
39
39
  * iframe-based theme preview.
40
+ * - `x-ampless-pathname` marker header set unconditionally on every
41
+ * request this middleware handles (public routes only). Server
42
+ * components such as `renderHead` / `renderBodyEnd` read this header
43
+ * to confirm a public, non-preview request before emitting analytics
44
+ * or consent scripts. Routes excluded from the matcher (admin / api /
45
+ * login) never receive a runtime-set marker, so those layouts stay clean.
40
46
  */
41
47
  declare function createAmplessMiddleware(opts: CreateMiddlewareOpts): MiddlewareFn;
42
48
  /**
@@ -1,3 +1,9 @@
1
+ import {
2
+ AMPLESS_PATHNAME_HEADER,
3
+ PREVIEW_COLOR_SCHEME_HEADER,
4
+ PREVIEW_THEME_HEADER
5
+ } from "./chunk-BS7KLDDE.js";
6
+
1
7
  // src/middleware.ts
2
8
  import { NextResponse } from "next/server";
3
9
  var runtime = "nodejs";
@@ -137,10 +143,13 @@ function createAmplessMiddleware(opts) {
137
143
  const previewTheme = url.searchParams.get("previewTheme");
138
144
  const previewColorScheme = url.searchParams.get("previewColorScheme");
139
145
  const requestHeaders = new Headers(request.headers);
140
- if (previewTheme) requestHeaders.set("x-preview-theme", previewTheme);
146
+ requestHeaders.delete(PREVIEW_THEME_HEADER);
147
+ requestHeaders.delete(PREVIEW_COLOR_SCHEME_HEADER);
148
+ if (previewTheme) requestHeaders.set(PREVIEW_THEME_HEADER, previewTheme);
141
149
  if (previewColorScheme) {
142
- requestHeaders.set("x-preview-color-scheme", previewColorScheme);
150
+ requestHeaders.set(PREVIEW_COLOR_SCHEME_HEADER, previewColorScheme);
143
151
  }
152
+ requestHeaders.set(AMPLESS_PATHNAME_HEADER, url.pathname);
144
153
  const passthrough = () => NextResponse.next({ request: { headers: requestHeaders } });
145
154
  const segments = url.pathname.split("/").filter(Boolean);
146
155
  if (segments.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/runtime",
3
- "version": "1.0.0-beta.60",
3
+ "version": "1.0.0-beta.62",
4
4
  "description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,8 +51,8 @@
51
51
  "marked": "^18.0.4",
52
52
  "sanitize-html": "^2.17.4",
53
53
  "tailwind-merge": "^3.6.0",
54
- "@ampless/plugin-og-image": "0.2.0-beta.51",
55
- "ampless": "1.0.0-beta.51"
54
+ "@ampless/plugin-og-image": "0.2.0-beta.52",
55
+ "ampless": "1.0.0-beta.52"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@aws-amplify/adapter-nextjs": "^1",