meno-core 1.0.50 → 1.0.52

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 (32) hide show
  1. package/build-static.ts +8 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/build-static.js +3 -3
  4. package/dist/chunks/{chunk-PQ2HRXDR.js → chunk-2MHDV5BF.js} +11 -1
  5. package/dist/chunks/chunk-2MHDV5BF.js.map +7 -0
  6. package/dist/chunks/{chunk-YWJJD5D6.js → chunk-A725KYFK.js} +36 -17
  7. package/dist/chunks/{chunk-YWJJD5D6.js.map → chunk-A725KYFK.js.map} +3 -3
  8. package/dist/chunks/{chunk-CVLFID6V.js → chunk-CXCBV2M7.js} +65 -14
  9. package/dist/chunks/chunk-CXCBV2M7.js.map +7 -0
  10. package/dist/chunks/{chunk-4OFZP5NQ.js → chunk-HNLUO36W.js} +15 -4
  11. package/dist/chunks/chunk-HNLUO36W.js.map +7 -0
  12. package/dist/chunks/{chunk-56EUSC6D.js → chunk-LHLHPYSP.js} +4 -4
  13. package/dist/chunks/{chunk-56EUSC6D.js.map → chunk-LHLHPYSP.js.map} +2 -2
  14. package/dist/chunks/{configService-VOY2MY2K.js → configService-R3OGU2UD.js} +2 -2
  15. package/dist/entries/server-router.js +3 -3
  16. package/dist/lib/server/index.js +5 -5
  17. package/lib/server/routes/pages.ts +37 -2
  18. package/lib/server/services/cmsService.ts +21 -0
  19. package/lib/server/services/configService.ts +20 -0
  20. package/lib/server/ssr/buildErrorOverlay.ts +22 -4
  21. package/lib/server/ssr/errorOverlay.ts +11 -3
  22. package/lib/server/ssr/htmlGenerator.nonce.test.ts +165 -0
  23. package/lib/server/ssr/htmlGenerator.ts +25 -6
  24. package/lib/server/ssr/liveReloadIntegration.test.ts +3 -1
  25. package/lib/server/ssr/metaTagGenerator.ts +54 -6
  26. package/lib/server/ssr/ssrRenderer.ts +1 -0
  27. package/lib/server/ssrRenderer.test.ts +157 -2
  28. package/package.json +1 -1
  29. package/dist/chunks/chunk-4OFZP5NQ.js.map +0 -7
  30. package/dist/chunks/chunk-CVLFID6V.js.map +0 -7
  31. package/dist/chunks/chunk-PQ2HRXDR.js.map +0 -7
  32. /package/dist/chunks/{configService-VOY2MY2K.js.map → configService-R3OGU2UD.js.map} +0 -0
package/build-static.ts CHANGED
@@ -812,9 +812,16 @@ export async function buildStaticPages(): Promise<void> {
812
812
  const extraFonts = cspConfig.fontSrc?.join(' ') || '';
813
813
  const extraImgs = cspConfig.imgSrc?.join(' ') || '';
814
814
 
815
+ // Production-built pages have NO executable inline scripts: page config,
816
+ // component JS, form handler, and MenoFilter all live in external
817
+ // /_scripts/{hash}.js files (returnSeparateJS: true), and the Meno
818
+ // badge's hover effect is pure CSS. Only `<script type="application/json">`
819
+ // remains inline (used as a data island for MenoFilter), which CSP
820
+ // ignores because it has no executable code. So we can drop
821
+ // `'unsafe-inline'` entirely from script-src in the generated _headers.
815
822
  const cspDirectives = [
816
823
  "default-src 'self'",
817
- `script-src 'self' 'unsafe-inline' https://f.vimeocdn.com https://player.vimeo.com https://www.youtube.com https://s.ytimg.com ${extraScripts}`.trim(),
824
+ `script-src 'self' https://f.vimeocdn.com https://player.vimeo.com https://www.youtube.com https://s.ytimg.com ${extraScripts}`.trim(),
818
825
  `style-src 'self' 'unsafe-inline' https://f.vimeocdn.com ${extraStyles}`.trim(),
819
826
  `img-src 'self' data: https: ${extraImgs}`.trim(),
820
827
  `connect-src 'self' https://vimeo.com https://*.vimeocdn.com ${extraConnect}`.trim(),
package/dist/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateBuildErrorPage
4
- } from "../chunks/chunk-4OFZP5NQ.js";
4
+ } from "../chunks/chunk-HNLUO36W.js";
5
5
  import {
6
6
  createRuntimeServer,
7
7
  setProjectRoot
@@ -9,9 +9,9 @@ import {
9
9
  hashContent,
10
10
  injectTrackingScript,
11
11
  isCMSPage
12
- } from "./chunks/chunk-56EUSC6D.js";
13
- import "./chunks/chunk-CVLFID6V.js";
14
- import "./chunks/chunk-PQ2HRXDR.js";
12
+ } from "./chunks/chunk-LHLHPYSP.js";
13
+ import "./chunks/chunk-CXCBV2M7.js";
14
+ import "./chunks/chunk-2MHDV5BF.js";
15
15
  import "./chunks/chunk-I7YIGZXT.js";
16
16
  import "./chunks/chunk-WQFG7PAH.js";
17
17
  import "./chunks/chunk-J23ZX5AP.js";
@@ -164,6 +164,16 @@ var ConfigService = class {
164
164
  }
165
165
  return this.config.icons;
166
166
  }
167
+ /**
168
+ * Get site-wide social configuration
169
+ * Returns empty object if not configured
170
+ */
171
+ getSocial() {
172
+ if (!this.config?.social || typeof this.config.social !== "object") {
173
+ return {};
174
+ }
175
+ return this.config.social;
176
+ }
167
177
  /**
168
178
  * Get libraries configuration
169
179
  * Returns empty arrays if not configured
@@ -238,4 +248,4 @@ export {
238
248
  ConfigService,
239
249
  configService
240
250
  };
241
- //# sourceMappingURL=chunk-PQ2HRXDR.js.map
251
+ //# sourceMappingURL=chunk-2MHDV5BF.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../lib/server/services/configService.ts"],
4
+ "sourcesContent": ["/**\n * Config Service\n * Centralized configuration loading and access\n *\n * Consolidates multiple config loaders into a single service that loads\n * the project.config.json file once and exposes typed sections.\n */\n\nimport type { BreakpointConfig, BreakpointConfigInput, BreakpointEntry } from '../../shared/breakpoints';\nimport { DEFAULT_BREAKPOINTS, normalizeBreakpointConfig } from '../../shared/breakpoints';\nimport type { ResponsiveScales, BreakpointScales } from '../../shared/responsiveScaling';\nimport { DEFAULT_RESPONSIVE_SCALES } from '../../shared/responsiveScaling';\nimport type { I18nConfig } from '../../shared/types/components';\nimport type { LibrariesConfig, JSLibraryConfig, CSSLibraryConfig } from '../../shared/types/libraries';\nimport type { CSPConfig } from '../../shared/types/config';\nimport type { CustomCodeConfig } from '../../shared/types/api';\nimport type { RemConversionConfig } from '../../shared/pxToRem';\nimport { DEFAULT_REM_CONFIG } from '../../shared/pxToRem';\nimport { DEFAULT_I18N_CONFIG, migrateI18nConfig } from '../../shared/i18n';\nimport { projectPaths } from '../projectContext';\nimport { readTextFile, fileExists } from '../runtime';\n\n/**\n * Icons configuration\n */\nexport interface IconsConfig {\n favicon?: string;\n faviconDark?: string;\n appleTouchIcon?: string;\n}\n\n/**\n * Site-wide social configuration\n */\nexport interface SocialConfig {\n twitterHandle?: string;\n}\n\n/**\n * Raw project config structure from project.config.json\n */\nexport type ImageFormat = 'webp' | 'avif';\n\ninterface RawProjectConfig {\n breakpoints?: BreakpointConfigInput;\n responsiveScales?: Partial<ResponsiveScales>;\n i18n?: unknown;\n icons?: IconsConfig;\n social?: SocialConfig;\n libraries?: LibrariesConfig;\n csp?: CSPConfig;\n baseComponent?: string;\n imageFormat?: ImageFormat;\n remConversion?: Partial<RemConversionConfig>;\n customCode?: CustomCodeConfig;\n showMenoBadge?: boolean;\n}\n\n/**\n * ConfigService\n * Loads project configuration once and provides typed access to sections\n */\nexport class ConfigService {\n private config: RawProjectConfig | null = null;\n private loaded = false;\n\n /**\n * Load configuration from project.config.json\n * Safe to call multiple times - only loads once\n */\n async load(): Promise<void> {\n if (this.loaded) {\n return;\n }\n\n try {\n if (await fileExists(projectPaths.config())) {\n const content = await readTextFile(projectPaths.config());\n this.config = JSON.parse(content);\n }\n } catch {\n // Fall through to defaults\n this.config = null;\n }\n\n this.loaded = true;\n }\n\n /**\n * Check if configuration has been loaded\n */\n isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Reset the service (for testing)\n */\n reset(): void {\n this.config = null;\n this.loaded = false;\n }\n\n /**\n * Get breakpoint configuration\n * Returns validated and normalized breakpoints (always object format)\n * Supports both legacy format { tablet: 1024 } and new format { tablet: { breakpoint: 1024, previewPoint: 768 } }\n */\n getBreakpoints(): BreakpointConfig {\n if (!this.config?.breakpoints || typeof this.config.breakpoints !== 'object') {\n return { ...DEFAULT_BREAKPOINTS };\n }\n\n // Validate breakpoint values before normalization\n const validInput: BreakpointConfigInput = {};\n for (const [key, value] of Object.entries(this.config.breakpoints)) {\n if (typeof value === 'number' && value > 0) {\n // Legacy format: number\n validInput[key] = value;\n } else if (typeof value === 'object' && value !== null) {\n // New format: object with breakpoint and optional previewPoint\n const entry = value as BreakpointEntry;\n if (typeof entry.breakpoint === 'number' && entry.breakpoint > 0) {\n validInput[key] = {\n breakpoint: entry.breakpoint,\n previewPoint: typeof entry.previewPoint === 'number' && entry.previewPoint > 0\n ? entry.previewPoint\n : entry.breakpoint,\n };\n }\n }\n }\n\n // Return normalized breakpoints or defaults if none valid\n if (Object.keys(validInput).length === 0) {\n return { ...DEFAULT_BREAKPOINTS };\n }\n\n return normalizeBreakpointConfig(validInput);\n }\n\n /**\n * Get i18n configuration\n * Automatically migrates old string[] format to LocaleConfig[] format\n */\n getI18n(): I18nConfig {\n if (!this.config?.i18n) {\n return { ...DEFAULT_I18N_CONFIG };\n }\n\n return migrateI18nConfig(this.config.i18n);\n }\n\n /**\n * Deep merge scale categories, preserving user-defined breakpoints\n * while filling in missing values from defaults\n */\n private mergeScaleCategory(\n userScales: BreakpointScales | undefined,\n defaultScales: BreakpointScales | undefined\n ): BreakpointScales | undefined {\n if (!userScales && !defaultScales) return undefined;\n if (!userScales) return defaultScales ? { ...defaultScales } : undefined;\n if (!defaultScales) return { ...userScales };\n\n // User scales take precedence, but include defaults for breakpoints not specified\n return {\n ...defaultScales,\n ...userScales,\n };\n }\n\n /**\n * Get responsive scales configuration\n * Supports dynamic breakpoints - scales are keyed by breakpoint name\n * Deep merges scale categories to preserve user breakpoint definitions\n */\n getResponsiveScales(): ResponsiveScales {\n if (!this.config?.responsiveScales || typeof this.config.responsiveScales !== 'object') {\n return { ...DEFAULT_RESPONSIVE_SCALES };\n }\n\n const userScales = this.config.responsiveScales;\n\n return {\n enabled: userScales.enabled ?? DEFAULT_RESPONSIVE_SCALES.enabled,\n mode: (userScales as { mode?: 'breakpoints' | 'fluid' }).mode ?? DEFAULT_RESPONSIVE_SCALES.mode,\n baseReference: userScales.baseReference ?? DEFAULT_RESPONSIVE_SCALES.baseReference,\n fluidRange: (userScales as { fluidRange?: { min: number; max: number } }).fluidRange\n ?? (DEFAULT_RESPONSIVE_SCALES.fluidRange ? { ...DEFAULT_RESPONSIVE_SCALES.fluidRange } : undefined),\n siteMargin: (userScales as { siteMargin?: { min: number; max: number } }).siteMargin\n ?? (DEFAULT_RESPONSIVE_SCALES.siteMargin ? { ...DEFAULT_RESPONSIVE_SCALES.siteMargin } : undefined),\n fontSize: this.mergeScaleCategory(\n userScales.fontSize as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.fontSize\n ),\n padding: this.mergeScaleCategory(\n userScales.padding as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.padding\n ),\n margin: this.mergeScaleCategory(\n userScales.margin as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.margin\n ),\n gap: this.mergeScaleCategory(\n userScales.gap as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.gap\n ),\n borderRadius: this.mergeScaleCategory(\n userScales.borderRadius as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.borderRadius\n ),\n size: this.mergeScaleCategory(\n userScales.size as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.size\n ),\n };\n }\n\n /**\n * Get rem conversion configuration\n */\n getRemConversion(): RemConversionConfig {\n if (!this.config?.remConversion || typeof this.config.remConversion !== 'object') {\n return { ...DEFAULT_REM_CONFIG };\n }\n return {\n enabled: this.config.remConversion.enabled ?? DEFAULT_REM_CONFIG.enabled,\n baseFontSize: this.config.remConversion.baseFontSize ?? DEFAULT_REM_CONFIG.baseFontSize,\n };\n }\n\n /**\n * Get icons configuration\n * Returns empty object if not configured\n */\n getIcons(): IconsConfig {\n if (!this.config?.icons || typeof this.config.icons !== 'object') {\n return {};\n }\n\n return this.config.icons;\n }\n\n /**\n * Get site-wide social configuration\n * Returns empty object if not configured\n */\n getSocial(): SocialConfig {\n if (!this.config?.social || typeof this.config.social !== 'object') {\n return {};\n }\n\n return this.config.social;\n }\n\n /**\n * Get libraries configuration\n * Returns empty arrays if not configured\n * Normalizes string URLs to object format for backwards compatibility\n */\n getLibraries(): LibrariesConfig {\n if (!this.config?.libraries || typeof this.config.libraries !== 'object') {\n return { js: [], css: [] };\n }\n\n const libs = this.config.libraries;\n\n // Normalize JS libraries: support both string URLs and object format\n const normalizedJs = Array.isArray(libs.js)\n ? libs.js.map((lib) =>\n typeof lib === 'string' ? { url: lib } : lib\n ) as JSLibraryConfig[]\n : [];\n\n // Normalize CSS libraries: support both string URLs and object format\n const normalizedCss = Array.isArray(libs.css)\n ? libs.css.map((lib) =>\n typeof lib === 'string' ? { url: lib } : lib\n ) as CSSLibraryConfig[]\n : [];\n\n return {\n js: normalizedJs,\n css: normalizedCss,\n };\n }\n\n /**\n * Get CSP configuration\n * Returns empty object if not configured\n */\n getCSP(): CSPConfig {\n if (!this.config?.csp || typeof this.config.csp !== 'object') {\n return {};\n }\n\n return this.config.csp;\n }\n\n /**\n * Get base component name for new pages\n * Returns undefined if not configured\n */\n getBaseComponent(): string | undefined {\n if (!this.config?.baseComponent || typeof this.config.baseComponent !== 'string') {\n return undefined;\n }\n return this.config.baseComponent;\n }\n\n /**\n * Get image format setting\n * Returns 'webp' (default) or 'avif'\n */\n getCustomCode(): CustomCodeConfig {\n if (!this.config?.customCode || typeof this.config.customCode !== 'object') {\n return {};\n }\n return this.config.customCode;\n }\n\n getShowMenoBadge(): boolean {\n return this.config?.showMenoBadge === true;\n }\n\n getImageFormat(): ImageFormat {\n if (this.config?.imageFormat === 'avif') return 'avif';\n return 'webp';\n }\n\n /**\n * Get raw config value by key (for extension)\n */\n getRaw<T>(key: string): T | undefined {\n if (!this.config) {\n return undefined;\n }\n return (this.config as Record<string, unknown>)[key] as T | undefined;\n }\n}\n\n/**\n * Singleton instance for global access\n * Use this for convenience, or create your own instance for testing\n */\nexport const configService = new ConfigService();\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -15,10 +15,10 @@ import {
15
15
  parseJSON,
16
16
  resolveSlugToPageId,
17
17
  variableService
18
- } from "./chunk-CVLFID6V.js";
18
+ } from "./chunk-CXCBV2M7.js";
19
19
  import {
20
20
  configService
21
- } from "./chunk-PQ2HRXDR.js";
21
+ } from "./chunk-2MHDV5BF.js";
22
22
  import {
23
23
  bundleFile,
24
24
  createRuntimeServer,
@@ -2469,6 +2469,9 @@ async function handleApiRoutes(req, url, pageService, componentService, cmsConte
2469
2469
  }
2470
2470
  }
2471
2471
 
2472
+ // lib/server/routes/pages.ts
2473
+ import { randomBytes } from "crypto";
2474
+
2472
2475
  // lib/shared/pathUtils.ts
2473
2476
  function getStaticFilePath(pagePath, distDir = "./dist") {
2474
2477
  if (pagePath === "/") {
@@ -2498,10 +2501,11 @@ function escapeHtml(str) {
2498
2501
  function safeJsonForScript(data) {
2499
2502
  return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
2500
2503
  }
2501
- function generateErrorPage(error, context) {
2504
+ function generateErrorPage(error, context, cspNonce) {
2502
2505
  const errorInfo = extractErrorInfo(error);
2503
2506
  const errorMessage = escapeHtml(errorInfo.message);
2504
2507
  const errorStack = errorInfo.stack ? escapeHtml(errorInfo.stack) : "";
2508
+ const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
2505
2509
  const errorDataJson = safeJsonForScript({
2506
2510
  type: "PREVIEW_ERROR",
2507
2511
  error: {
@@ -2661,7 +2665,7 @@ function generateErrorPage(error, context) {
2661
2665
  </button>
2662
2666
  </div>
2663
2667
  </div>
2664
- <script>
2668
+ <script${nonceAttr}>
2665
2669
  (function() {
2666
2670
  // Send error to parent editor
2667
2671
  if (window.parent && window.parent !== window) {
@@ -2718,10 +2722,15 @@ function hashContent2(content) {
2718
2722
 
2719
2723
  // lib/server/routes/pages.ts
2720
2724
  var EDITOR_HEADER = "x-meno-editor";
2725
+ var CSP_NONCE_HEADER = "x-meno-csp-nonce";
2726
+ function generateCspNonce() {
2727
+ return randomBytes(16).toString("base64");
2728
+ }
2721
2729
  async function handlePageRoute(url, context, req) {
2722
2730
  const { pageService, componentService, cmsService, injectLiveReload, isEditor, serverPort } = context;
2723
2731
  const pagePath = url.pathname;
2724
2732
  const injectEditorAttrs = req?.headers.get(EDITOR_HEADER) === "1";
2733
+ const cspNonce = generateCspNonce();
2725
2734
  const i18nConfig = await loadI18nConfig();
2726
2735
  const { locale, pathWithoutLocale } = parseLocaleFromPath(pagePath, i18nConfig);
2727
2736
  const slugMappings = pageService.getSlugMappings();
@@ -2762,7 +2771,8 @@ async function handlePageRoute(url, context, req) {
2762
2771
  injectEditorAttrs,
2763
2772
  isEditor,
2764
2773
  serverPort,
2765
- returnSeparateJS: true
2774
+ returnSeparateJS: true,
2775
+ cspNonce
2766
2776
  });
2767
2777
  let finalHtml = result.html;
2768
2778
  if (result.javascript) {
@@ -2776,7 +2786,8 @@ async function handlePageRoute(url, context, req) {
2776
2786
  "Content-Type": "text/html; charset=utf-8",
2777
2787
  "Cache-Control": "no-store, max-age=0",
2778
2788
  "Pragma": "no-cache",
2779
- "Expires": "0"
2789
+ "Expires": "0",
2790
+ [CSP_NONCE_HEADER]: cspNonce
2780
2791
  }
2781
2792
  });
2782
2793
  }
@@ -2794,22 +2805,25 @@ async function handlePageRoute(url, context, req) {
2794
2805
  pageLibraries: typedPageData.meta?.libraries,
2795
2806
  pageCustomCode: typedPageData.meta?.customCode,
2796
2807
  injectEditorAttrs,
2797
- isEditor
2808
+ isEditor,
2809
+ cspNonce
2798
2810
  });
2799
2811
  return new Response(ssrHTML, {
2800
2812
  headers: {
2801
2813
  "Content-Type": "text/html; charset=utf-8",
2802
2814
  "Cache-Control": "no-store, max-age=0",
2803
2815
  "Pragma": "no-cache",
2804
- "Expires": "0"
2816
+ "Expires": "0",
2817
+ [CSP_NONCE_HEADER]: cspNonce
2805
2818
  }
2806
2819
  });
2807
2820
  } catch (error) {
2808
2821
  console.error("Error rendering CMS page:", error);
2809
- return new Response(generateErrorPage(error, `Error rendering template: ${cmsTemplatePath}`), {
2822
+ return new Response(generateErrorPage(error, `Error rendering template: ${cmsTemplatePath}`, cspNonce), {
2810
2823
  headers: {
2811
2824
  "Content-Type": "text/html; charset=utf-8",
2812
- "Cache-Control": "no-store"
2825
+ "Cache-Control": "no-store",
2826
+ [CSP_NONCE_HEADER]: cspNonce
2813
2827
  }
2814
2828
  });
2815
2829
  }
@@ -2857,7 +2871,8 @@ async function handlePageRoute(url, context, req) {
2857
2871
  injectEditorAttrs,
2858
2872
  isEditor,
2859
2873
  serverPort,
2860
- returnSeparateJS: true
2874
+ returnSeparateJS: true,
2875
+ cspNonce
2861
2876
  });
2862
2877
  let finalHtml = result.html;
2863
2878
  if (result.javascript) {
@@ -2871,7 +2886,8 @@ async function handlePageRoute(url, context, req) {
2871
2886
  "Content-Type": "text/html; charset=utf-8",
2872
2887
  "Cache-Control": "no-store, max-age=0",
2873
2888
  "Pragma": "no-cache",
2874
- "Expires": "0"
2889
+ "Expires": "0",
2890
+ [CSP_NONCE_HEADER]: cspNonce
2875
2891
  }
2876
2892
  });
2877
2893
  }
@@ -2887,22 +2903,25 @@ async function handlePageRoute(url, context, req) {
2887
2903
  pageLibraries: pageData.meta?.libraries,
2888
2904
  pageCustomCode: pageData.meta?.customCode,
2889
2905
  injectEditorAttrs,
2890
- isEditor
2906
+ isEditor,
2907
+ cspNonce
2891
2908
  });
2892
2909
  return new Response(ssrHTML, {
2893
2910
  headers: {
2894
2911
  "Content-Type": "text/html; charset=utf-8",
2895
2912
  "Cache-Control": "no-store, max-age=0",
2896
2913
  "Pragma": "no-cache",
2897
- "Expires": "0"
2914
+ "Expires": "0",
2915
+ [CSP_NONCE_HEADER]: cspNonce
2898
2916
  }
2899
2917
  });
2900
2918
  } catch (error) {
2901
2919
  console.error("Error rendering page:", error);
2902
- return new Response(generateErrorPage(error, `Error rendering page: ${lookupPath}`), {
2920
+ return new Response(generateErrorPage(error, `Error rendering page: ${lookupPath}`, cspNonce), {
2903
2921
  headers: {
2904
2922
  "Content-Type": "text/html; charset=utf-8",
2905
- "Cache-Control": "no-store"
2923
+ "Cache-Control": "no-store",
2924
+ [CSP_NONCE_HEADER]: cspNonce
2906
2925
  }
2907
2926
  });
2908
2927
  }
@@ -3350,4 +3369,4 @@ export {
3350
3369
  createServer,
3351
3370
  FileSystemPageProvider
3352
3371
  };
3353
- //# sourceMappingURL=chunk-YWJJD5D6.js.map
3372
+ //# sourceMappingURL=chunk-A725KYFK.js.map