@blinkk/root 1.0.0-rc.35 → 1.0.0-rc.37
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/bin/root.js +5 -32
- package/dist/{chunk-MJCIAH6K.js → chunk-7IDJ3PBT.js} +1 -1
- package/dist/{chunk-MJCIAH6K.js.map → chunk-7IDJ3PBT.js.map} +1 -1
- package/dist/{chunk-JH33OQEK.js → chunk-L2UBQKLP.js} +7 -8
- package/dist/{chunk-JH33OQEK.js.map → chunk-L2UBQKLP.js.map} +1 -1
- package/dist/{chunk-3XHMMHV7.js → chunk-TZAHHHA4.js} +6 -8
- package/dist/{chunk-3XHMMHV7.js.map → chunk-TZAHHHA4.js.map} +1 -1
- package/dist/{chunk-5ZBCP3XZ.js → chunk-VDORN4UU.js} +89 -50
- package/dist/chunk-VDORN4UU.js.map +1 -0
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +5 -3
- package/dist/core.d.ts +4 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/functions.js +6 -6
- package/dist/functions.js.map +1 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +153 -31
- package/dist/render.js.map +1 -1
- package/dist/{types-a9rD7sef.d.ts → types-403nR8i5.d.ts} +58 -1
- package/package.json +3 -3
- package/dist/chunk-5ZBCP3XZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CliRunner,
|
|
2
3
|
build,
|
|
3
4
|
createDevServer,
|
|
4
5
|
createPreviewServer,
|
|
@@ -6,12 +7,13 @@ import {
|
|
|
6
7
|
dev,
|
|
7
8
|
preview,
|
|
8
9
|
start
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-VDORN4UU.js";
|
|
11
|
+
import "./chunk-TZAHHHA4.js";
|
|
12
|
+
import "./chunk-L2UBQKLP.js";
|
|
12
13
|
import "./chunk-QKBMWK5B.js";
|
|
13
14
|
import "./chunk-IUQLRDFW.js";
|
|
14
15
|
export {
|
|
16
|
+
CliRunner,
|
|
15
17
|
build,
|
|
16
18
|
createDevServer,
|
|
17
19
|
createPreviewServer,
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as Route } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { R as Route } from './types-403nR8i5.js';
|
|
2
|
+
export { i as ConfigureServerHook, j as ConfigureServerOptions, C as ContentSecurityPolicyConfig, n as GetStaticPaths, G as GetStaticProps, r as Handler, H as HandlerContext, u as HandlerRenderFn, t as HandlerRenderOptions, L as LocaleGroup, M as MultipartFile, N as NextFunction, P as Plugin, p as Request, o as RequestMiddleware, q as Response, b as RootConfig, e as RootHeaderConfig, c as RootI18nConfig, d as RootRedirectConfig, f as RootSecurityConfig, g as RootServerConfig, a as RootUserConfig, s as RouteModule, m as RouteParams, S as Server, X as XFrameOptionsConfig, k as configureServerPlugins, h as defineConfig, l as getVitePlugins } from './types-403nR8i5.js';
|
|
3
3
|
import * as preact$1 from 'preact';
|
|
4
4
|
import { FunctionalComponent, ComponentChildren } from 'preact';
|
|
5
5
|
import 'express';
|
|
@@ -116,6 +116,8 @@ interface RequestContext {
|
|
|
116
116
|
locale: string;
|
|
117
117
|
/** Translations map for the current locale. */
|
|
118
118
|
translations: Record<string, string>;
|
|
119
|
+
/** CSP nonce value. */
|
|
120
|
+
nonce?: string;
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
121
123
|
* A hook that returns information about the current route.
|
package/dist/core.js
CHANGED
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n const context = useI18nContext();\n const translations = context.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AAsKO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;ACvKA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAChC,QAAM,UAAU,eAAe;AAC/B,QAAM,eAAe,QAAQ,gBAAgB,CAAC;AAC9C,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\n\nimport {HtmlMinifyOptions} from '../render/html-minify';\nimport {HtmlPrettyOptions} from '../render/html-pretty';\n\nimport {Plugin} from './plugin';\nimport {RequestMiddleware} from './types';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface ContentSecurityPolicyConfig {\n directives?: Record<string, string[]>;\n reportOnly?: boolean;\n}\n\nexport interface XFrameOptionsConfig {\n action: 'DENY' | 'SAMEORIGIN';\n}\n\nexport interface RootSecurityConfig {\n /**\n * Content-Security-Policy config. If enabled, a nonce is auto-generated\n * for every request and appended to script and stylesheet tags. You can\n * validate your CSP headers using a tool like {@link https://csp-evaluator.withgoogle.com/}.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP}\n */\n contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean;\n\n /**\n * Strict-Transport-Security config. When enabled, the header value is set\n * to `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`.\n */\n strictTransportSecurity?: boolean;\n\n /**\n * X-Content-Type-Options config. When enabled, the header value is set to\n * `X-Content-Type-Options: nosniff`.\n */\n xContentTypeOptions?: boolean;\n\n /**\n * X-Frame-Options config. Setting this value to `true` will default the\n * header value to `X-Frame-Options: SAMEORIGIN`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options}\n */\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | boolean;\n\n /**\n * X-XSS-Protection config. When enabled, the header value is set to\n * `X-XSS-Protection: 1; mode=block`.\n */\n xXssProtection?: boolean;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n\n /**\n * HTTP security settings. By default, all security settings are enabled with\n * commonly used default values.\n */\n security?: RootSecurityConfig;\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {HTML_CONTEXT} from './Html';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement>;\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {useI18nContext} from './useI18nContext';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n const context = useI18nContext();\n const translations = context.translations || {};\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n if (params) {\n for (const param of Object.keys(params)) {\n const val = String(params[param] ?? '');\n translation = translation.replaceAll(`{${param}}`, val);\n }\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => line.trimEnd());\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;AA0NO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;AC3NA,SAAQ,kBAAiB;AAkChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;ACnCA,SAAQ,cAAAA,mBAAiB;AAqBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC/BA,SAAQ,cAAAC,mBAAiB;AAiBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;ACbO,SAAS,kBAAkB;AAChC,QAAM,UAAU,eAAe;AAC/B,QAAM,eAAe,QAAQ,gBAAgB,CAAC;AAC9C,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAO,KAAK,MAAM,GAAG;AACvC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACtC,sBAAc,YAAY,WAAW,IAAI,KAAK,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC/B,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["useContext","useContext","useContext","useContext"]}
|
package/dist/functions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPreviewServer,
|
|
3
3
|
createProdServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-VDORN4UU.js";
|
|
5
|
+
import "./chunk-TZAHHHA4.js";
|
|
6
|
+
import "./chunk-L2UBQKLP.js";
|
|
7
7
|
import "./chunk-QKBMWK5B.js";
|
|
8
8
|
import "./chunk-IUQLRDFW.js";
|
|
9
9
|
|
|
@@ -12,10 +12,10 @@ import path from "node:path";
|
|
|
12
12
|
import { onRequest } from "firebase-functions/v2/https";
|
|
13
13
|
function server(options) {
|
|
14
14
|
let rootServer;
|
|
15
|
-
const rootDir = path.resolve(
|
|
16
|
-
return onRequest(
|
|
15
|
+
const rootDir = path.resolve(options?.rootDir || process.cwd());
|
|
16
|
+
return onRequest(options?.httpsOptions || {}, async (req, res) => {
|
|
17
17
|
if (!rootServer) {
|
|
18
|
-
if (
|
|
18
|
+
if (options?.mode === "preview") {
|
|
19
19
|
rootServer = await createPreviewServer({ rootDir });
|
|
20
20
|
} else {
|
|
21
21
|
rootServer = await createProdServer({ rootDir });
|
package/dist/functions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,
|
|
1
|
+
{"version":3,"sources":["../src/functions/server.ts"],"sourcesContent":["import path from 'node:path';\nimport {HttpsOptions, onRequest} from 'firebase-functions/v2/https';\nimport {createPreviewServer, createProdServer} from '../cli/cli';\nimport {Server} from '../core/types';\n\nexport interface ProdServerOptions {\n rootDir?: string;\n mode?: 'preview' | 'production';\n httpsOptions?: HttpsOptions;\n}\n\n/**\n * Firebase Function that runs a Root.js server running in SSR mode.\n */\nexport function server(options?: ProdServerOptions) {\n let rootServer: Server;\n const rootDir = path.resolve(options?.rootDir || process.cwd());\n return onRequest(options?.httpsOptions || {}, async (req, res) => {\n if (!rootServer) {\n if (options?.mode === 'preview') {\n rootServer = await createPreviewServer({rootDir});\n } else {\n rootServer = await createProdServer({rootDir});\n }\n }\n await rootServer(req, res);\n });\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAsB,iBAAgB;AAa/B,SAAS,OAAO,SAA6B;AAClD,MAAI;AACJ,QAAM,UAAU,KAAK,QAAQ,SAAS,WAAW,QAAQ,IAAI,CAAC;AAC9D,SAAO,UAAU,SAAS,gBAAgB,CAAC,GAAG,OAAO,KAAK,QAAQ;AAChE,QAAI,CAAC,YAAY;AACf,UAAI,SAAS,SAAS,WAAW;AAC/B,qBAAa,MAAM,oBAAoB,EAAC,QAAO,CAAC;AAAA,MAClD,OAAO;AACL,qBAAa,MAAM,iBAAiB,EAAC,QAAO,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,WAAW,KAAK,GAAG;AAAA,EAC3B,CAAC;AACH;","names":[]}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as RootConfig,
|
|
2
|
-
export {
|
|
1
|
+
import { b as RootConfig, p as Request, q as Response, N as NextFunction } from './types-403nR8i5.js';
|
|
2
|
+
export { v as SESSION_COOKIE, x as SaveSessionOptions, z as Session, w as SessionMiddlewareOptions, y as sessionMiddleware } from './types-403nR8i5.js';
|
|
3
3
|
import { RequestHandler } from 'express';
|
|
4
4
|
import 'preact';
|
|
5
5
|
import 'vite';
|
package/dist/middleware.js
CHANGED
package/dist/node.d.ts
CHANGED
package/dist/node.js
CHANGED
package/dist/render.d.ts
CHANGED
package/dist/render.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
I18N_CONTEXT,
|
|
4
4
|
REQUEST_CONTEXT,
|
|
5
5
|
getTranslations
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7IDJ3PBT.js";
|
|
7
7
|
import {
|
|
8
8
|
RouteTrie,
|
|
9
9
|
htmlMinify,
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
} from "./chunk-IUQLRDFW.js";
|
|
13
13
|
|
|
14
14
|
// src/render/render.tsx
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import {
|
|
17
|
+
options as preactOptions
|
|
18
|
+
} from "preact";
|
|
15
19
|
import renderToString from "preact-render-to-string";
|
|
16
20
|
|
|
17
21
|
// src/core/pages/ErrorPage.tsx
|
|
@@ -92,7 +96,6 @@ function ErrorPage(props) {
|
|
|
92
96
|
// src/core/pages/DevErrorPage.tsx
|
|
93
97
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
94
98
|
function DevErrorPage(props) {
|
|
95
|
-
var _a;
|
|
96
99
|
const req = props.req;
|
|
97
100
|
const err = props.error;
|
|
98
101
|
const route = props.route;
|
|
@@ -100,7 +103,7 @@ function DevErrorPage(props) {
|
|
|
100
103
|
let errMsg = String(err);
|
|
101
104
|
if (err && err.stack) {
|
|
102
105
|
errMsg = err.stack.replace(/\(.*node_modules/g, "(node_modules").replace(/at \/.*node_modules/g, "at node_modules");
|
|
103
|
-
if (
|
|
106
|
+
if (req.rootConfig?.rootDir) {
|
|
104
107
|
errMsg = errMsg.replaceAll(req.rootConfig.rootDir, "<root>");
|
|
105
108
|
}
|
|
106
109
|
if (process.env.HOME) {
|
|
@@ -114,7 +117,7 @@ function DevErrorPage(props) {
|
|
|
114
117
|
] }),
|
|
115
118
|
/* @__PURE__ */ jsx2("h2", { children: "Debug Info" }),
|
|
116
119
|
/* @__PURE__ */ jsx2("pre", { className: "box", children: /* @__PURE__ */ jsx2("code", { children: `url: ${req.originalUrl}
|
|
117
|
-
route: ${
|
|
120
|
+
route: ${route?.src || "null"}
|
|
118
121
|
routeParams: ${routeParams && JSON.stringify(routeParams) || "null"}` }) })
|
|
119
122
|
] });
|
|
120
123
|
}
|
|
@@ -245,11 +248,10 @@ var ES_419_COUNTRIES = [
|
|
|
245
248
|
// Venezuela
|
|
246
249
|
];
|
|
247
250
|
function getFallbackLocales(req) {
|
|
248
|
-
var _a, _b;
|
|
249
251
|
const hl = getFirstQueryParam(req, "hl");
|
|
250
252
|
const countryCode = getCountry(req);
|
|
251
253
|
if (isWebCrawler(req)) {
|
|
252
|
-
const defaultLocale =
|
|
254
|
+
const defaultLocale = req.rootConfig?.i18n?.defaultLocale || "en";
|
|
253
255
|
if (hl && hl !== defaultLocale) {
|
|
254
256
|
return [hl, defaultLocale];
|
|
255
257
|
}
|
|
@@ -350,13 +352,11 @@ var Router = class {
|
|
|
350
352
|
await this.routeTrie.walk(cb);
|
|
351
353
|
}
|
|
352
354
|
initRouteTrie() {
|
|
353
|
-
|
|
354
|
-
const locales = ((_a = this.rootConfig.i18n) == null ? void 0 : _a.locales) || [];
|
|
355
|
+
const locales = this.rootConfig.i18n?.locales || [];
|
|
355
356
|
const basePath = this.rootConfig.base || "/";
|
|
356
|
-
const defaultLocale =
|
|
357
|
+
const defaultLocale = this.rootConfig.i18n?.defaultLocale || "en";
|
|
357
358
|
const trie = new RouteTrie();
|
|
358
359
|
Object.keys(ROUTES_FILES).forEach((modulePath) => {
|
|
359
|
-
var _a2;
|
|
360
360
|
const src = modulePath.slice(1);
|
|
361
361
|
let relativeRoutePath = modulePath.replace(/^\/routes/, "");
|
|
362
362
|
const parts = path.parse(relativeRoutePath);
|
|
@@ -370,17 +370,16 @@ var Router = class {
|
|
|
370
370
|
}
|
|
371
371
|
const urlFormat = "/[base]/[path]";
|
|
372
372
|
const i18nUrlFormat = toSquareBrackets(
|
|
373
|
-
|
|
373
|
+
this.rootConfig.i18n?.urlFormat || "/[locale]/[base]/[path]"
|
|
374
374
|
);
|
|
375
375
|
const placeholders = {
|
|
376
376
|
base: removeSlashes(basePath),
|
|
377
377
|
path: removeSlashes(relativeRoutePath)
|
|
378
378
|
};
|
|
379
379
|
const formatUrl = (format) => {
|
|
380
|
-
var _a3;
|
|
381
380
|
const url = format.replaceAll("[base]", placeholders.base).replaceAll("[path]", placeholders.path);
|
|
382
381
|
return normalizeUrlPath(url, {
|
|
383
|
-
trailingSlash:
|
|
382
|
+
trailingSlash: this.rootConfig.server?.trailingSlash
|
|
384
383
|
});
|
|
385
384
|
};
|
|
386
385
|
const routePath = formatUrl(urlFormat);
|
|
@@ -472,7 +471,7 @@ function replaceParams(urlPathFormat, params) {
|
|
|
472
471
|
}
|
|
473
472
|
function normalizeUrlPath(urlPath, options) {
|
|
474
473
|
urlPath = urlPath.replace(/\/+/g, "/");
|
|
475
|
-
if (
|
|
474
|
+
if (options?.trailingSlash === false && urlPath !== "/" && urlPath.endsWith("/")) {
|
|
476
475
|
urlPath = urlPath.replace(/\/*$/g, "");
|
|
477
476
|
}
|
|
478
477
|
if (urlPath.endsWith("/index")) {
|
|
@@ -522,14 +521,13 @@ var Renderer = class {
|
|
|
522
521
|
}
|
|
523
522
|
const fallbackLocales = route.isDefaultLocale ? getFallbackLocales(req) : [route.locale];
|
|
524
523
|
const getPreferredLocale = (availableLocales) => {
|
|
525
|
-
var _a, _b;
|
|
526
524
|
const lowerLocales = availableLocales.map((l) => l.toLowerCase());
|
|
527
525
|
for (const fallbackLocale of fallbackLocales) {
|
|
528
526
|
if (lowerLocales.includes(fallbackLocale.toLowerCase())) {
|
|
529
527
|
return fallbackLocale;
|
|
530
528
|
}
|
|
531
529
|
}
|
|
532
|
-
return
|
|
530
|
+
return req.rootConfig?.i18n?.defaultLocale || "en";
|
|
533
531
|
};
|
|
534
532
|
const render404 = async () => {
|
|
535
533
|
next();
|
|
@@ -540,15 +538,19 @@ var Renderer = class {
|
|
|
540
538
|
render404();
|
|
541
539
|
return;
|
|
542
540
|
}
|
|
541
|
+
const securityConfig = this.getSecurityConfig();
|
|
542
|
+
const cspEnabled = !!securityConfig.contentSecurityPolicy;
|
|
543
543
|
const currentPath = req.path;
|
|
544
|
-
const locale =
|
|
545
|
-
const translations = options
|
|
544
|
+
const locale = options?.locale || route.locale;
|
|
545
|
+
const translations = options?.translations;
|
|
546
|
+
const nonce = cspEnabled ? this.generateNonce() : void 0;
|
|
546
547
|
const output = await this.renderComponent(route.module.default, props2, {
|
|
547
548
|
currentPath,
|
|
548
549
|
route,
|
|
549
550
|
routeParams,
|
|
550
551
|
locale,
|
|
551
|
-
translations
|
|
552
|
+
translations,
|
|
553
|
+
nonce
|
|
552
554
|
});
|
|
553
555
|
let html = output.html;
|
|
554
556
|
if (this.rootConfig.prettyHtml) {
|
|
@@ -558,6 +560,12 @@ var Renderer = class {
|
|
|
558
560
|
}
|
|
559
561
|
if (req.viteServer) {
|
|
560
562
|
html = await req.viteServer.transformIndexHtml(currentPath, html);
|
|
563
|
+
if (nonce) {
|
|
564
|
+
html = html.replace(
|
|
565
|
+
'<script type="module" src="/@vite/client"></script>',
|
|
566
|
+
`<script type="module" src="/@vite/client" nonce="${nonce}"></script>`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
561
569
|
}
|
|
562
570
|
let statusCode = 200;
|
|
563
571
|
if (route.src === "routes/404.tsx") {
|
|
@@ -566,7 +574,13 @@ var Renderer = class {
|
|
|
566
574
|
statusCode = 500;
|
|
567
575
|
}
|
|
568
576
|
req.hooks.trigger("preRender");
|
|
569
|
-
res.status(statusCode)
|
|
577
|
+
res.status(statusCode);
|
|
578
|
+
res.set({ "Content-Type": "text/html" });
|
|
579
|
+
this.setSecurityHeaders(res, {
|
|
580
|
+
securityConfig,
|
|
581
|
+
nonce
|
|
582
|
+
});
|
|
583
|
+
res.end(html);
|
|
570
584
|
};
|
|
571
585
|
if (route.module.handle) {
|
|
572
586
|
const handlerContext = {
|
|
@@ -596,7 +610,7 @@ var Renderer = class {
|
|
|
596
610
|
await render(props);
|
|
597
611
|
}
|
|
598
612
|
async renderComponent(Component, props, options) {
|
|
599
|
-
const { currentPath, route, routeParams } = options;
|
|
613
|
+
const { currentPath, route, routeParams, nonce } = options;
|
|
600
614
|
const locale = options.locale;
|
|
601
615
|
const translations = {
|
|
602
616
|
...getTranslations(locale),
|
|
@@ -608,7 +622,8 @@ var Renderer = class {
|
|
|
608
622
|
props,
|
|
609
623
|
routeParams,
|
|
610
624
|
locale,
|
|
611
|
-
translations
|
|
625
|
+
translations,
|
|
626
|
+
nonce
|
|
612
627
|
};
|
|
613
628
|
const htmlContext = {
|
|
614
629
|
htmlAttrs: {},
|
|
@@ -618,7 +633,26 @@ var Renderer = class {
|
|
|
618
633
|
scriptDeps: []
|
|
619
634
|
};
|
|
620
635
|
const vdom = /* @__PURE__ */ jsx4(REQUEST_CONTEXT.Provider, { value: ctx, children: /* @__PURE__ */ jsx4(I18N_CONTEXT.Provider, { value: { locale, translations }, children: /* @__PURE__ */ jsx4(HTML_CONTEXT.Provider, { value: htmlContext, children: /* @__PURE__ */ jsx4(Component, { ...props }) }) }) });
|
|
621
|
-
const
|
|
636
|
+
const preactHook = preactOptions.vnode;
|
|
637
|
+
let mainHtml;
|
|
638
|
+
try {
|
|
639
|
+
preactOptions.vnode = (vnode) => {
|
|
640
|
+
if (vnode && vnode.type === "script") {
|
|
641
|
+
vnode.props.nonce = nonce;
|
|
642
|
+
}
|
|
643
|
+
if (vnode && vnode.type === "link" && vnode.props.rel === "stylesheet") {
|
|
644
|
+
vnode.props.nonce = nonce;
|
|
645
|
+
}
|
|
646
|
+
if (preactHook) {
|
|
647
|
+
preactHook(vnode);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
mainHtml = renderToString(vdom);
|
|
651
|
+
preactOptions.vnode = preactHook;
|
|
652
|
+
} catch (err) {
|
|
653
|
+
preactOptions.vnode = preactHook;
|
|
654
|
+
throw err;
|
|
655
|
+
}
|
|
622
656
|
const jsDeps = /* @__PURE__ */ new Set();
|
|
623
657
|
const cssDeps = /* @__PURE__ */ new Set();
|
|
624
658
|
const routeAsset = await this.assetMap.get(route.src);
|
|
@@ -647,10 +681,10 @@ var Renderer = class {
|
|
|
647
681
|
})
|
|
648
682
|
);
|
|
649
683
|
const styleTags = Array.from(cssDeps).map((cssUrl) => {
|
|
650
|
-
return /* @__PURE__ */ jsx4("link", { rel: "stylesheet", href: cssUrl });
|
|
684
|
+
return /* @__PURE__ */ jsx4("link", { rel: "stylesheet", href: cssUrl, nonce });
|
|
651
685
|
});
|
|
652
686
|
const scriptTags = Array.from(jsDeps).map((jsUrls) => {
|
|
653
|
-
return /* @__PURE__ */ jsx4("script", { type: "module", src: jsUrls });
|
|
687
|
+
return /* @__PURE__ */ jsx4("script", { type: "module", src: jsUrls, nonce });
|
|
654
688
|
});
|
|
655
689
|
const html = await this.renderHtml(mainHtml, {
|
|
656
690
|
htmlAttrs: htmlContext.htmlAttrs,
|
|
@@ -724,13 +758,13 @@ var Renderer = class {
|
|
|
724
758
|
return sitemap;
|
|
725
759
|
}
|
|
726
760
|
async renderHtml(html, options) {
|
|
727
|
-
const htmlAttrs =
|
|
728
|
-
const headAttrs =
|
|
729
|
-
const bodyAttrs =
|
|
761
|
+
const htmlAttrs = options?.htmlAttrs || {};
|
|
762
|
+
const headAttrs = options?.headAttrs || {};
|
|
763
|
+
const bodyAttrs = options?.bodyAttrs || {};
|
|
730
764
|
const page = /* @__PURE__ */ jsxs4("html", { ...htmlAttrs, children: [
|
|
731
765
|
/* @__PURE__ */ jsxs4("head", { ...headAttrs, children: [
|
|
732
766
|
/* @__PURE__ */ jsx4("meta", { charSet: "utf-8" }),
|
|
733
|
-
options
|
|
767
|
+
options?.headComponents
|
|
734
768
|
] }),
|
|
735
769
|
/* @__PURE__ */ jsx4("body", { ...bodyAttrs, dangerouslySetInnerHTML: { __html: html } })
|
|
736
770
|
] });
|
|
@@ -739,7 +773,7 @@ ${renderToString(page)}
|
|
|
739
773
|
`;
|
|
740
774
|
}
|
|
741
775
|
async render404(options) {
|
|
742
|
-
const currentPath =
|
|
776
|
+
const currentPath = options?.currentPath || "/404";
|
|
743
777
|
const [route, routeParams] = this.router.get("/404");
|
|
744
778
|
if (route && route.src === "routes/404.tsx" && route.module.default) {
|
|
745
779
|
const Component = route.module.default;
|
|
@@ -775,7 +809,7 @@ ${renderToString(page)}
|
|
|
775
809
|
return { html };
|
|
776
810
|
}
|
|
777
811
|
async renderError(err, options) {
|
|
778
|
-
const currentPath =
|
|
812
|
+
const currentPath = options?.currentPath || "/500";
|
|
779
813
|
const [route, routeParams] = this.router.get("/500");
|
|
780
814
|
if (route && route.src === "routes/500.tsx" && route.module.default) {
|
|
781
815
|
const Component = route.module.default;
|
|
@@ -874,7 +908,95 @@ ${renderToString(page)}
|
|
|
874
908
|
);
|
|
875
909
|
return { jsDeps, cssDeps };
|
|
876
910
|
}
|
|
911
|
+
/**
|
|
912
|
+
* Returns the `security` config value with default values inserted wherever
|
|
913
|
+
* a user config value is blank or set to `true`.
|
|
914
|
+
*/
|
|
915
|
+
getSecurityConfig() {
|
|
916
|
+
const userConfig = this.rootConfig.server?.security || {};
|
|
917
|
+
const securityConfig = {};
|
|
918
|
+
if (isTrueOrUndefined(userConfig.contentSecurityPolicy)) {
|
|
919
|
+
securityConfig.contentSecurityPolicy = {
|
|
920
|
+
directives: {
|
|
921
|
+
"base-uri": ["'none'"],
|
|
922
|
+
"object-src": ["'none'"],
|
|
923
|
+
// NOTE: nonce is automatically added to this list.
|
|
924
|
+
"script-src": [
|
|
925
|
+
"'unsafe-inline'",
|
|
926
|
+
"'unsafe-eval'",
|
|
927
|
+
"'strict-dynamic' https: http:"
|
|
928
|
+
]
|
|
929
|
+
},
|
|
930
|
+
reportOnly: true
|
|
931
|
+
};
|
|
932
|
+
} else {
|
|
933
|
+
securityConfig.contentSecurityPolicy = userConfig.contentSecurityPolicy;
|
|
934
|
+
}
|
|
935
|
+
if (isTrueOrUndefined(userConfig.xFrameOptions)) {
|
|
936
|
+
securityConfig.xFrameOptions = "SAMEORIGIN";
|
|
937
|
+
} else {
|
|
938
|
+
securityConfig.xFrameOptions = userConfig.xFrameOptions;
|
|
939
|
+
}
|
|
940
|
+
securityConfig.strictTransportSecurity = userConfig.strictTransportSecurity ?? true;
|
|
941
|
+
securityConfig.xContentTypeOptions = userConfig.xContentTypeOptions ?? true;
|
|
942
|
+
securityConfig.xXssProtection = userConfig.xXssProtection ?? true;
|
|
943
|
+
return securityConfig;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Generates a random string that can be used as the "nonce" value for CSP.
|
|
947
|
+
*/
|
|
948
|
+
generateNonce() {
|
|
949
|
+
return crypto.randomBytes(16).toString("base64");
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Sets security-related HTTP headers.
|
|
953
|
+
*/
|
|
954
|
+
setSecurityHeaders(res, options) {
|
|
955
|
+
const securityConfig = options.securityConfig;
|
|
956
|
+
const contentSecurityPolicy = securityConfig.contentSecurityPolicy;
|
|
957
|
+
if (typeof contentSecurityPolicy === "object") {
|
|
958
|
+
const directives = contentSecurityPolicy.directives || {};
|
|
959
|
+
if (options.nonce) {
|
|
960
|
+
if (!directives["script-src"]) {
|
|
961
|
+
directives["script-src"] = [
|
|
962
|
+
"'unsafe-inline'",
|
|
963
|
+
"'unsafe-eval'",
|
|
964
|
+
"'strict-dynamic' https: http:"
|
|
965
|
+
];
|
|
966
|
+
}
|
|
967
|
+
directives["script-src"].push(`'nonce-${options.nonce}'`);
|
|
968
|
+
}
|
|
969
|
+
const headerSegments = [];
|
|
970
|
+
Object.entries(directives).forEach(([key, values]) => {
|
|
971
|
+
headerSegments.push([key, ...values].join(" "));
|
|
972
|
+
});
|
|
973
|
+
const csp = headerSegments.join("; ");
|
|
974
|
+
if (contentSecurityPolicy.reportOnly === false) {
|
|
975
|
+
res.setHeader("content-security-policy", csp);
|
|
976
|
+
} else {
|
|
977
|
+
res.setHeader("content-security-policy-report-only", csp);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (typeof securityConfig.xFrameOptions === "string") {
|
|
981
|
+
res.setHeader("x-frame-options", securityConfig.xFrameOptions);
|
|
982
|
+
}
|
|
983
|
+
if (securityConfig.strictTransportSecurity) {
|
|
984
|
+
res.setHeader(
|
|
985
|
+
"strict-transport-security",
|
|
986
|
+
"max-age=63072000; includeSubdomains; preload"
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (securityConfig.xContentTypeOptions) {
|
|
990
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
991
|
+
}
|
|
992
|
+
if (securityConfig.xXssProtection) {
|
|
993
|
+
res.setHeader("x-xss-protection", "1; mode=block");
|
|
994
|
+
}
|
|
995
|
+
}
|
|
877
996
|
};
|
|
997
|
+
function isTrueOrUndefined(value) {
|
|
998
|
+
return value === true || value === void 0;
|
|
999
|
+
}
|
|
878
1000
|
export {
|
|
879
1001
|
Renderer
|
|
880
1002
|
};
|