@blinkk/root 1.0.0-rc.4 → 1.0.0-rc.41
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/LICENSE +22 -0
- package/bin/root.js +6 -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-IUQLRDFW.js +237 -0
- package/dist/chunk-IUQLRDFW.js.map +1 -0
- package/dist/{chunk-WNXIRMFF.js → chunk-TZAHHHA4.js} +27 -7
- package/dist/chunk-TZAHHHA4.js.map +1 -0
- package/dist/{chunk-J2ANSYAE.js → chunk-UYHD775U.js} +82 -9
- package/dist/chunk-UYHD775U.js.map +1 -0
- package/dist/{chunk-CY3DVKXO.js → chunk-XOOP364R.js} +445 -117
- package/dist/chunk-XOOP364R.js.map +1 -0
- package/dist/cli.d.ts +18 -3
- package/dist/cli.js +8 -4
- package/dist/core.d.ts +5 -3
- package/dist/core.js +8 -3
- package/dist/core.js.map +1 -1
- package/dist/functions.d.ts +1 -1
- package/dist/functions.js +7 -7
- package/dist/functions.js.map +1 -1
- package/dist/middleware.d.ts +12 -5
- package/dist/middleware.js +3 -1
- package/dist/node.d.ts +10 -2
- package/dist/node.js +5 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +351 -289
- package/dist/render.js.map +1 -1
- package/dist/{types-9209ea89.d.ts → types-403nR8i5.d.ts} +96 -8
- package/package.json +38 -35
- package/dist/chunk-CY3DVKXO.js.map +0 -1
- package/dist/chunk-DFBTOMQF.js +0 -61
- package/dist/chunk-DFBTOMQF.js.map +0 -1
- package/dist/chunk-J2ANSYAE.js.map +0 -1
- package/dist/chunk-WNXIRMFF.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024 Blinkk LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/bin/root.js
CHANGED
|
@@ -3,42 +3,16 @@
|
|
|
3
3
|
import {createRequire} from 'node:module';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {bgGreen, black} from 'kleur/colors';
|
|
6
|
+
import {CliRunner} from '../dist/cli.js';
|
|
7
|
+
import 'dotenv/config';
|
|
9
8
|
|
|
10
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
10
|
const require = createRequire(import.meta.url);
|
|
12
11
|
const packageJson = require(path.join(__dirname, '../package.json'));
|
|
13
12
|
|
|
14
|
-
async function main() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const program = new Command('root');
|
|
18
|
-
program.version(packageJson.version);
|
|
19
|
-
program
|
|
20
|
-
.command('build [path]')
|
|
21
|
-
.description('generates a static build')
|
|
22
|
-
.option('--ssr-only', 'produce a ssr-only build')
|
|
23
|
-
.option('--mode <mode>', 'see: https://vitejs.dev/guide/env-and-mode.html#modes', 'production')
|
|
24
|
-
.option('-c, --concurrency <num>', 'number of files to build concurrently', 10)
|
|
25
|
-
.action(build);
|
|
26
|
-
program
|
|
27
|
-
.command('dev [path]')
|
|
28
|
-
.description('starts the server in development mode')
|
|
29
|
-
.option('--host <host>', 'network address the server should listen on, e.g. 127.0.0.1')
|
|
30
|
-
.action(dev);
|
|
31
|
-
program
|
|
32
|
-
.command('preview [path]')
|
|
33
|
-
.description('starts the server in preview mode')
|
|
34
|
-
.option('--host <host>', 'network address the server should listen on, e.g. 127.0.0.1')
|
|
35
|
-
.action(preview);
|
|
36
|
-
program
|
|
37
|
-
.command('start [path]')
|
|
38
|
-
.description('starts the server in production mode')
|
|
39
|
-
.option('--host <host>', 'network address the server should listen on, e.g. 127.0.0.1')
|
|
40
|
-
.action(start);
|
|
41
|
-
await program.parseAsync(process.argv);
|
|
13
|
+
async function main(argv) {
|
|
14
|
+
const cli = new CliRunner('root.js', packageJson.version);
|
|
15
|
+
await cli.run(argv);
|
|
42
16
|
}
|
|
43
17
|
|
|
44
|
-
main();
|
|
18
|
+
main(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/components/Html.tsx","../src/core/hooks/useI18nContext.ts","../src/core/hooks/useRequestContext.ts"],"sourcesContent":["import {ComponentChildren, FunctionalComponent, createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport interface HtmlContext {\n htmlAttrs: preact.JSX.HTMLAttributes<HTMLHtmlElement>;\n headAttrs: preact.JSX.HTMLAttributes<HTMLHeadElement>;\n headComponents: ComponentChildren[];\n bodyAttrs: preact.JSX.HTMLAttributes<HTMLBodyElement>;\n scriptDeps: Array<preact.JSX.HTMLAttributes<HTMLScriptElement>>;\n}\n\nexport const HTML_CONTEXT = createContext<HtmlContext | null>(null);\n\nexport type HtmlProps = preact.JSX.HTMLAttributes<HTMLHtmlElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Html>` component can be used to update attrs in the `<html>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Html lang=\"en-US\">\n * <h1>Hello world</h1>\n * </Html>\n * ```\n */\nexport const Html: FunctionalComponent<HtmlProps> = ({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 <Html> component'\n );\n }\n context.htmlAttrs = attrs;\n return <>{children}</>;\n};\n","import path from 'node:path';\n\nimport {createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport const I18N_CONTEXT = createContext<I18nContext | null>(null);\n\nexport interface I18nContext {\n locale: string;\n translations: Record<string, string>;\n}\n\n/**\n * A hook that returns information about the current i18n context, including the\n * locale for the given route and a map of translations for that locale.\n */\nexport function useI18nContext() {\n const context = useContext(I18N_CONTEXT);\n if (!context) {\n throw new Error('I18N_CONTEXT not found');\n }\n return context;\n}\n\nexport function getTranslations(locale: string): Record<string, string> {\n const translations: Record<string, Record<string, string>> = {};\n const translationsFiles = import.meta.glob(['/translations/*.json'], {\n eager: true,\n }) as Record<string, {default?: Record<string, string>}>;\n Object.keys(translationsFiles).forEach((translationPath) => {\n const parts = path.parse(translationPath);\n const locale = parts.name;\n const module = translationsFiles[translationPath];\n if (module && module.default) {\n translations[locale] = module.default;\n }\n });\n return translations[locale] || {};\n}\n","import {createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {Route} from '../types';\n\nexport interface RequestContext {\n /**\n * The current request path, e.g. `/foo/bar` (default route path) or\n * `/{locale}/foo/bar` (localized route path).\n */\n currentPath: string;\n /** The route file. */\n route: Route;\n /**\n * Route param values. E.g. for a route like `routes/blog/[slug].tsx`,\n * visiting `/blog/foo` will pass {slug: 'foo'} here.\n */\n routeParams: Record<string, string>;\n /** Props passed to the route's server component. */\n props: any;\n /** The current locale. */\n locale: string;\n /** Translations map for the current locale. */\n translations: Record<string, string>;\n}\n\nexport const REQUEST_CONTEXT = createContext<RequestContext | null>(null);\n\n/**\n * A hook that returns information about the current route.\n *\n * Usage:\n *\n * ```ts\n * const ctx = useRequestContext();\n * ctx.route.src;\n * // => 'routes/index.tsx'\n * ```\n */\nexport function useRequestContext() {\n const context = useContext(REQUEST_CONTEXT);\n if (!context) {\n throw new Error('REQUEST_CONTEXT not found');\n }\n return context;\n}\n"],"mappings":";AAAA,SAAgD,qBAAoB;AACpE,SAAQ,kBAAiB;AAmChB;AAzBF,IAAM,eAAe,cAAkC,IAAI;AAiB3D,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;;;ACrCA,OAAO,UAAU;AAEjB,SAAQ,iBAAAA,sBAAoB;AAC5B,SAAQ,cAAAC,mBAAiB;AAElB,IAAM,eAAeD,eAAkC,IAAI;AAW3D,SAAS,iBAAiB;AAC/B,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,QAAwC;AACtE,QAAM,eAAuD,CAAC;AAC9D,QAAM,oBAAoB,YAAY,KAAK,CAAC,sBAAsB,GAAG;AAAA,IACnE,OAAO;AAAA,EACT,CAAC;AACD,SAAO,KAAK,iBAAiB,EAAE,QAAQ,CAAC,oBAAoB;AAC1D,UAAM,QAAQ,KAAK,MAAM,eAAe;AACxC,UAAMC,UAAS,MAAM;AACrB,UAAM,SAAS,kBAAkB,eAAe;AAChD,QAAI,UAAU,OAAO,SAAS;AAC5B,mBAAaA,OAAM,IAAI,OAAO;AAAA,IAChC;AAAA,EACF,CAAC;AACD,SAAO,aAAa,MAAM,KAAK,CAAC;AAClC;;;ACtCA,SAAQ,iBAAAC,sBAAoB;AAC5B,SAAQ,cAAAC,mBAAiB;
|
|
1
|
+
{"version":3,"sources":["../src/core/components/Html.tsx","../src/core/hooks/useI18nContext.ts","../src/core/hooks/useRequestContext.ts"],"sourcesContent":["import {ComponentChildren, FunctionalComponent, createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport interface HtmlContext {\n htmlAttrs: preact.JSX.HTMLAttributes<HTMLHtmlElement>;\n headAttrs: preact.JSX.HTMLAttributes<HTMLHeadElement>;\n headComponents: ComponentChildren[];\n bodyAttrs: preact.JSX.HTMLAttributes<HTMLBodyElement>;\n scriptDeps: Array<preact.JSX.HTMLAttributes<HTMLScriptElement>>;\n}\n\nexport const HTML_CONTEXT = createContext<HtmlContext | null>(null);\n\nexport type HtmlProps = preact.JSX.HTMLAttributes<HTMLHtmlElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Html>` component can be used to update attrs in the `<html>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Html lang=\"en-US\">\n * <h1>Hello world</h1>\n * </Html>\n * ```\n */\nexport const Html: FunctionalComponent<HtmlProps> = ({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 <Html> component'\n );\n }\n context.htmlAttrs = attrs;\n return <>{children}</>;\n};\n","import path from 'node:path';\n\nimport {createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport const I18N_CONTEXT = createContext<I18nContext | null>(null);\n\nexport interface I18nContext {\n locale: string;\n translations: Record<string, string>;\n}\n\n/**\n * A hook that returns information about the current i18n context, including the\n * locale for the given route and a map of translations for that locale.\n */\nexport function useI18nContext() {\n const context = useContext(I18N_CONTEXT);\n if (!context) {\n throw new Error('I18N_CONTEXT not found');\n }\n return context;\n}\n\nexport function getTranslations(locale: string): Record<string, string> {\n const translations: Record<string, Record<string, string>> = {};\n const translationsFiles = import.meta.glob(['/translations/*.json'], {\n eager: true,\n }) as Record<string, {default?: Record<string, string>}>;\n Object.keys(translationsFiles).forEach((translationPath) => {\n const parts = path.parse(translationPath);\n const locale = parts.name;\n const module = translationsFiles[translationPath];\n if (module && module.default) {\n translations[locale] = module.default;\n }\n });\n return translations[locale] || {};\n}\n","import {createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nimport {Route} from '../types';\n\nexport interface RequestContext {\n /**\n * The current request path, e.g. `/foo/bar` (default route path) or\n * `/{locale}/foo/bar` (localized route path).\n */\n currentPath: string;\n /** The route file. */\n route: Route;\n /**\n * Route param values. E.g. for a route like `routes/blog/[slug].tsx`,\n * visiting `/blog/foo` will pass {slug: 'foo'} here.\n */\n routeParams: Record<string, string>;\n /** Props passed to the route's server component. */\n props: any;\n /** The current locale. */\n locale: string;\n /** Translations map for the current locale. */\n translations: Record<string, string>;\n /** CSP nonce value. */\n nonce?: string;\n}\n\nexport const REQUEST_CONTEXT = createContext<RequestContext | null>(null);\n\n/**\n * A hook that returns information about the current route.\n *\n * Usage:\n *\n * ```ts\n * const ctx = useRequestContext();\n * ctx.route.src;\n * // => 'routes/index.tsx'\n * ```\n */\nexport function useRequestContext() {\n const context = useContext(REQUEST_CONTEXT);\n if (!context) {\n throw new Error('REQUEST_CONTEXT not found');\n }\n return context;\n}\n"],"mappings":";AAAA,SAAgD,qBAAoB;AACpE,SAAQ,kBAAiB;AAmChB;AAzBF,IAAM,eAAe,cAAkC,IAAI;AAiB3D,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;;;ACrCA,OAAO,UAAU;AAEjB,SAAQ,iBAAAA,sBAAoB;AAC5B,SAAQ,cAAAC,mBAAiB;AAElB,IAAM,eAAeD,eAAkC,IAAI;AAW3D,SAAS,iBAAiB;AAC/B,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,QAAwC;AACtE,QAAM,eAAuD,CAAC;AAC9D,QAAM,oBAAoB,YAAY,KAAK,CAAC,sBAAsB,GAAG;AAAA,IACnE,OAAO;AAAA,EACT,CAAC;AACD,SAAO,KAAK,iBAAiB,EAAE,QAAQ,CAAC,oBAAoB;AAC1D,UAAM,QAAQ,KAAK,MAAM,eAAe;AACxC,UAAMC,UAAS,MAAM;AACrB,UAAM,SAAS,kBAAkB,eAAe;AAChD,QAAI,UAAU,OAAO,SAAS;AAC5B,mBAAaA,OAAM,IAAI,OAAO;AAAA,IAChC;AAAA,EACF,CAAC;AACD,SAAO,aAAa,MAAM,KAAK,CAAC;AAClC;;;ACtCA,SAAQ,iBAAAC,sBAAoB;AAC5B,SAAQ,cAAAC,mBAAiB;AA2BlB,IAAM,kBAAkBD,eAAqC,IAAI;AAajE,SAAS,oBAAoB;AAClC,QAAM,UAAUC,YAAW,eAAe;AAC1C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AACA,SAAO;AACT;","names":["createContext","useContext","locale","createContext","useContext"]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// src/utils/elements.ts
|
|
2
|
+
var ELEMENT_RE = /^[a-z][\w-]*-[\w-]*$/;
|
|
3
|
+
var HTML_ELEMENTS_REGEX = /<([a-z][\w-]*-[\w-]*)/g;
|
|
4
|
+
function isValidTagName(tagName) {
|
|
5
|
+
return ELEMENT_RE.test(tagName);
|
|
6
|
+
}
|
|
7
|
+
function parseTagNames(src) {
|
|
8
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
9
|
+
const matches = Array.from(src.matchAll(HTML_ELEMENTS_REGEX));
|
|
10
|
+
for (const match of matches) {
|
|
11
|
+
const tagName = match[1];
|
|
12
|
+
tagNames.add(tagName);
|
|
13
|
+
}
|
|
14
|
+
return Array.from(tagNames);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/render/html-minify.ts
|
|
18
|
+
import { createRequire } from "module";
|
|
19
|
+
var require2 = createRequire(import.meta.url);
|
|
20
|
+
var { minify } = require2("html-minifier-terser");
|
|
21
|
+
async function htmlMinify(html, options) {
|
|
22
|
+
const minifyOptions = options || {
|
|
23
|
+
collapseWhitespace: true,
|
|
24
|
+
removeComments: true,
|
|
25
|
+
preserveLineBreaks: true
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
const min = await minify(html, minifyOptions);
|
|
29
|
+
return min.trimStart();
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error("failed to minify html:", e);
|
|
32
|
+
return html;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/render/html-pretty.ts
|
|
37
|
+
import { createRequire as createRequire2 } from "module";
|
|
38
|
+
var require3 = createRequire2(import.meta.url);
|
|
39
|
+
var beautify = require3("js-beautify");
|
|
40
|
+
async function htmlPretty(html, options) {
|
|
41
|
+
const prettyOptions = options || {
|
|
42
|
+
indent_size: 0,
|
|
43
|
+
end_with_newline: true,
|
|
44
|
+
extra_liners: []
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
const output = beautify.html(html, prettyOptions);
|
|
48
|
+
return output.trimStart();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error("failed to pretty html:", e);
|
|
51
|
+
return html;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/render/route-trie.ts
|
|
56
|
+
var RouteTrie = class _RouteTrie {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.children = {};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Adds a route to the trie.
|
|
62
|
+
*/
|
|
63
|
+
add(path, route) {
|
|
64
|
+
path = this.normalizePath(path);
|
|
65
|
+
if (path === "") {
|
|
66
|
+
this.route = route;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const [head, tail] = this.splitPath(path);
|
|
70
|
+
if (head.startsWith("[[...") && head.endsWith("]]")) {
|
|
71
|
+
const paramName = head.slice(5, -2);
|
|
72
|
+
this.optCatchAllNodes = new CatchAllNode(paramName, route);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (head.startsWith("[...") && head.endsWith("]")) {
|
|
76
|
+
const paramName = head.slice(4, -1);
|
|
77
|
+
this.catchAllNodes = new CatchAllNode(paramName, route);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let nextNode;
|
|
81
|
+
if (head.startsWith("[") && head.endsWith("]")) {
|
|
82
|
+
if (!this.paramNodes) {
|
|
83
|
+
this.paramNodes = {};
|
|
84
|
+
}
|
|
85
|
+
const paramName = head.slice(1, -1);
|
|
86
|
+
if (!this.paramNodes[paramName]) {
|
|
87
|
+
this.paramNodes[paramName] = new ParamNode(paramName);
|
|
88
|
+
}
|
|
89
|
+
nextNode = this.paramNodes[paramName].trie;
|
|
90
|
+
} else {
|
|
91
|
+
nextNode = this.children[head];
|
|
92
|
+
if (!nextNode) {
|
|
93
|
+
nextNode = new _RouteTrie();
|
|
94
|
+
this.children[head] = nextNode;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
nextNode.add(tail, route);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns a route mapped to the given path and any parameter values from the
|
|
101
|
+
* URL.
|
|
102
|
+
*/
|
|
103
|
+
get(path) {
|
|
104
|
+
const params = {};
|
|
105
|
+
const route = this.getRoute(path, params);
|
|
106
|
+
return [route, params];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Walks the route trie and calls a callback function for each route.
|
|
110
|
+
*/
|
|
111
|
+
walk(cb) {
|
|
112
|
+
const promises = [];
|
|
113
|
+
const addPromise = (promise) => {
|
|
114
|
+
if (promise) {
|
|
115
|
+
promises.push(promise);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
if (this.route) {
|
|
119
|
+
addPromise(cb("/", this.route));
|
|
120
|
+
}
|
|
121
|
+
if (this.paramNodes) {
|
|
122
|
+
Object.values(this.paramNodes).forEach((paramChild) => {
|
|
123
|
+
const param = `[${paramChild.name}]`;
|
|
124
|
+
paramChild.trie.walk((childPath, route) => {
|
|
125
|
+
const paramUrlPath = `/${param}${childPath}`;
|
|
126
|
+
addPromise(cb(paramUrlPath, route));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (this.catchAllNodes) {
|
|
131
|
+
const wildcardUrlPath = `/[...${this.catchAllNodes.name}]`;
|
|
132
|
+
addPromise(cb(wildcardUrlPath, this.catchAllNodes.route));
|
|
133
|
+
}
|
|
134
|
+
if (this.optCatchAllNodes) {
|
|
135
|
+
const wildcardUrlPath = `/[[...${this.optCatchAllNodes.name}]]`;
|
|
136
|
+
addPromise(cb(wildcardUrlPath, this.optCatchAllNodes.route));
|
|
137
|
+
}
|
|
138
|
+
for (const subpath of Object.keys(this.children)) {
|
|
139
|
+
const childTrie = this.children[subpath];
|
|
140
|
+
childTrie.walk((childPath, childRoute) => {
|
|
141
|
+
addPromise(cb(`/${subpath}${childPath}`, childRoute));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return Promise.all(promises).then(() => {
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Removes all routes from the trie.
|
|
149
|
+
*/
|
|
150
|
+
clear() {
|
|
151
|
+
this.children = {};
|
|
152
|
+
this.paramNodes = void 0;
|
|
153
|
+
this.catchAllNodes = void 0;
|
|
154
|
+
this.optCatchAllNodes = void 0;
|
|
155
|
+
this.route = void 0;
|
|
156
|
+
}
|
|
157
|
+
getRoute(urlPath, params) {
|
|
158
|
+
urlPath = this.normalizePath(urlPath);
|
|
159
|
+
if (urlPath === "") {
|
|
160
|
+
if (this.route) {
|
|
161
|
+
return this.route;
|
|
162
|
+
}
|
|
163
|
+
if (this.optCatchAllNodes) {
|
|
164
|
+
if (urlPath) {
|
|
165
|
+
params[this.optCatchAllNodes.name] = urlPath;
|
|
166
|
+
}
|
|
167
|
+
return this.optCatchAllNodes.route;
|
|
168
|
+
}
|
|
169
|
+
return void 0;
|
|
170
|
+
}
|
|
171
|
+
const [head, tail] = this.splitPath(urlPath);
|
|
172
|
+
const child = this.children[head];
|
|
173
|
+
if (child) {
|
|
174
|
+
const route = child.getRoute(tail, params);
|
|
175
|
+
if (route) {
|
|
176
|
+
return route;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (this.paramNodes) {
|
|
180
|
+
for (const paramChild of Object.values(this.paramNodes)) {
|
|
181
|
+
const route = paramChild.trie.getRoute(tail, params);
|
|
182
|
+
if (route) {
|
|
183
|
+
params[paramChild.name] = head;
|
|
184
|
+
return route;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (this.catchAllNodes) {
|
|
189
|
+
params[this.catchAllNodes.name] = urlPath;
|
|
190
|
+
return this.catchAllNodes.route;
|
|
191
|
+
}
|
|
192
|
+
if (this.optCatchAllNodes) {
|
|
193
|
+
params[this.optCatchAllNodes.name] = urlPath;
|
|
194
|
+
return this.optCatchAllNodes.route;
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Normalizes a path for inclusion into the route trie.
|
|
200
|
+
*/
|
|
201
|
+
normalizePath(path) {
|
|
202
|
+
return path.replace(/^\/+/g, "").replace(/\/+$/g, "");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Splits the parent directory from its children, e.g.:
|
|
206
|
+
*
|
|
207
|
+
* splitPath("foo/bar/baz") -> ["foo", "bar/baz"]
|
|
208
|
+
*/
|
|
209
|
+
splitPath(path) {
|
|
210
|
+
const i = path.indexOf("/");
|
|
211
|
+
if (i === -1) {
|
|
212
|
+
return [path, ""];
|
|
213
|
+
}
|
|
214
|
+
return [path.slice(0, i), path.slice(i + 1)];
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var ParamNode = class {
|
|
218
|
+
constructor(name) {
|
|
219
|
+
this.trie = new RouteTrie();
|
|
220
|
+
this.name = name;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var CatchAllNode = class {
|
|
224
|
+
constructor(name, route) {
|
|
225
|
+
this.name = name;
|
|
226
|
+
this.route = route;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
isValidTagName,
|
|
232
|
+
parseTagNames,
|
|
233
|
+
htmlMinify,
|
|
234
|
+
htmlPretty,
|
|
235
|
+
RouteTrie
|
|
236
|
+
};
|
|
237
|
+
//# sourceMappingURL=chunk-IUQLRDFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/elements.ts","../src/render/html-minify.ts","../src/render/html-pretty.ts","../src/render/route-trie.ts"],"sourcesContent":["export const ELEMENT_RE = /^[a-z][\\w-]*-[\\w-]*$/;\nexport const HTML_ELEMENTS_REGEX = /<([a-z][\\w-]*-[\\w-]*)/g;\n\n/**\n * Returns true if the tagName is a valid custom element tag.\n */\nexport function isValidTagName(tagName: string) {\n return ELEMENT_RE.test(tagName);\n}\n\n/**\n * Returns a list of custom elements used in a src string (e.g. HTML, jsx, lit).\n * NOTE: the impl uses a simple regex, so tagNames used in comments and attr\n * values may be included.\n */\nexport function parseTagNames(src: string): string[] {\n const tagNames = new Set<string>();\n const matches = Array.from(src.matchAll(HTML_ELEMENTS_REGEX));\n for (const match of matches) {\n const tagName = match[1];\n tagNames.add(tagName);\n }\n return Array.from(tagNames);\n}\n","import {createRequire} from 'module';\n\nconst require = createRequire(import.meta.url);\nimport type {Options} from 'html-minifier-terser';\n\nconst {minify} = require('html-minifier-terser');\n\nexport type HtmlMinifyOptions = Options;\n\nexport async function htmlMinify(\n html: string,\n options?: HtmlMinifyOptions\n): Promise<string> {\n const minifyOptions = options || {\n collapseWhitespace: true,\n removeComments: true,\n preserveLineBreaks: true,\n };\n try {\n const min = await minify(html, minifyOptions);\n return min.trimStart();\n } catch (e) {\n console.error('failed to minify html:', e);\n return html;\n }\n}\n","import {createRequire} from 'module';\n\nconst require = createRequire(import.meta.url);\nimport type {HTMLBeautifyOptions} from 'js-beautify';\n\nconst beautify = require('js-beautify');\n\nexport type HtmlPrettyOptions = HTMLBeautifyOptions;\n\nexport async function htmlPretty(\n html: string,\n options?: HtmlPrettyOptions\n): Promise<string> {\n const prettyOptions = options || {\n indent_size: 0,\n end_with_newline: true,\n extra_liners: [],\n };\n try {\n const output = beautify.html(html, prettyOptions);\n return output.trimStart();\n } catch (e) {\n console.error('failed to pretty html:', e);\n return html;\n }\n}\n","/**\n * A trie data structure that stores routes. Supports Next-style routing using\n * [param], [...catchall], and [[...optcatchall]] placeholders.\n */\nexport class RouteTrie<T> {\n private children: Record<string, RouteTrie<T>> = {};\n private paramNodes?: {[param: string]: ParamNode<T>};\n private catchAllNodes?: CatchAllNode<T>;\n private optCatchAllNodes?: CatchAllNode<T>;\n private route?: T;\n\n /**\n * Adds a route to the trie.\n */\n add(path: string, route: T) {\n path = this.normalizePath(path);\n\n // If the end was reached, save the value to the node.\n if (path === '') {\n this.route = route;\n return;\n }\n\n const [head, tail] = this.splitPath(path);\n\n if (head.startsWith('[[...') && head.endsWith(']]')) {\n const paramName = head.slice(5, -2);\n this.optCatchAllNodes = new CatchAllNode(paramName, route);\n return;\n }\n if (head.startsWith('[...') && head.endsWith(']')) {\n const paramName = head.slice(4, -1);\n this.catchAllNodes = new CatchAllNode(paramName, route);\n return;\n }\n\n let nextNode: RouteTrie<T>;\n if (head.startsWith('[') && head.endsWith(']')) {\n if (!this.paramNodes) {\n this.paramNodes = {};\n }\n const paramName = head.slice(1, -1);\n if (!this.paramNodes[paramName]) {\n this.paramNodes[paramName] = new ParamNode(paramName);\n }\n nextNode = this.paramNodes[paramName].trie;\n } else {\n nextNode = this.children[head];\n if (!nextNode) {\n nextNode = new RouteTrie();\n this.children[head] = nextNode;\n }\n }\n nextNode.add(tail, route);\n }\n\n /**\n * Returns a route mapped to the given path and any parameter values from the\n * URL.\n */\n get(path: string): [T | undefined, Record<string, string>] {\n const params = {};\n const route = this.getRoute(path, params);\n return [route, params];\n }\n\n /**\n * Walks the route trie and calls a callback function for each route.\n */\n walk(cb: (urlPath: string, route: T) => Promise<void> | void): Promise<void> {\n const promises: Array<Promise<void>> = [];\n const addPromise = (promise: Promise<void> | void) => {\n if (promise) {\n promises.push(promise);\n }\n };\n if (this.route) {\n addPromise(cb('/', this.route));\n }\n if (this.paramNodes) {\n Object.values(this.paramNodes).forEach((paramChild) => {\n const param = `[${paramChild.name}]`;\n paramChild.trie.walk((childPath: string, route: T) => {\n const paramUrlPath = `/${param}${childPath}`;\n addPromise(cb(paramUrlPath, route));\n });\n });\n }\n if (this.catchAllNodes) {\n const wildcardUrlPath = `/[...${this.catchAllNodes.name}]`;\n addPromise(cb(wildcardUrlPath, this.catchAllNodes.route));\n }\n if (this.optCatchAllNodes) {\n const wildcardUrlPath = `/[[...${this.optCatchAllNodes.name}]]`;\n addPromise(cb(wildcardUrlPath, this.optCatchAllNodes.route));\n }\n for (const subpath of Object.keys(this.children)) {\n const childTrie = this.children[subpath];\n childTrie.walk((childPath: string, childRoute: T) => {\n addPromise(cb(`/${subpath}${childPath}`, childRoute));\n });\n }\n return Promise.all(promises).then(() => {});\n }\n\n /**\n * Removes all routes from the trie.\n */\n clear() {\n this.children = {};\n this.paramNodes = undefined;\n this.catchAllNodes = undefined;\n this.optCatchAllNodes = undefined;\n this.route = undefined;\n }\n\n private getRoute(\n urlPath: string,\n params: Record<string, string>\n ): T | undefined {\n urlPath = this.normalizePath(urlPath);\n if (urlPath === '') {\n if (this.route) {\n return this.route;\n }\n if (this.optCatchAllNodes) {\n if (urlPath) {\n params[this.optCatchAllNodes.name] = urlPath;\n }\n return this.optCatchAllNodes.route;\n }\n return undefined;\n }\n\n const [head, tail] = this.splitPath(urlPath);\n\n const child = this.children[head];\n if (child) {\n const route = child.getRoute(tail, params);\n if (route) {\n return route;\n }\n }\n\n if (this.paramNodes) {\n for (const paramChild of Object.values(this.paramNodes)) {\n const route = paramChild.trie.getRoute(tail, params);\n if (route) {\n params[paramChild.name] = head;\n return route;\n }\n }\n }\n\n if (this.catchAllNodes) {\n params[this.catchAllNodes.name] = urlPath;\n return this.catchAllNodes.route;\n }\n\n if (this.optCatchAllNodes) {\n params[this.optCatchAllNodes.name] = urlPath;\n return this.optCatchAllNodes.route;\n }\n\n return undefined;\n }\n\n /**\n * Normalizes a path for inclusion into the route trie.\n */\n private normalizePath(path: string) {\n // Remove leading/trailing slashes.\n return path.replace(/^\\/+/g, '').replace(/\\/+$/g, '');\n }\n\n /**\n * Splits the parent directory from its children, e.g.:\n *\n * splitPath(\"foo/bar/baz\") -> [\"foo\", \"bar/baz\"]\n */\n private splitPath(path: string): [string, string] {\n const i = path.indexOf('/');\n if (i === -1) {\n return [path, ''];\n }\n return [path.slice(0, i), path.slice(i + 1)];\n }\n}\n\n/**\n * A node in the RouteTrie for a :param child.\n */\nclass ParamNode<T> {\n readonly name: string;\n readonly trie: RouteTrie<T> = new RouteTrie();\n\n constructor(name: string) {\n this.name = name;\n }\n}\n\n/**\n * A node in the RouteTrie for a *wildcard child.\n */\nclass CatchAllNode<T> {\n readonly name: string;\n readonly route: T;\n\n constructor(name: string, route: T) {\n this.name = name;\n this.route = route;\n }\n}\n"],"mappings":"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","names":["require","createRequire","require"]}
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
// src/middleware/common.ts
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import micromatch from "micromatch";
|
|
3
4
|
function rootProjectMiddleware(options) {
|
|
4
5
|
return (req, _, next) => {
|
|
5
6
|
req.rootConfig = options.rootConfig;
|
|
6
7
|
next();
|
|
7
8
|
};
|
|
8
9
|
}
|
|
10
|
+
function headersMiddleware(options) {
|
|
11
|
+
const headersUserConfig = options.rootConfig.server?.headers || [];
|
|
12
|
+
const headersConfig = headersUserConfig.filter((headerConfig) => {
|
|
13
|
+
return headerConfig.source && headerConfig.headers && headerConfig.headers.length > 0;
|
|
14
|
+
});
|
|
15
|
+
return (req, res, next) => {
|
|
16
|
+
headersConfig.forEach((headerConfig) => {
|
|
17
|
+
if (micromatch.isMatch(req.path, headerConfig.source)) {
|
|
18
|
+
headerConfig.headers.forEach((header) => {
|
|
19
|
+
if (header.key) {
|
|
20
|
+
res.setHeader(String(header.key), String(header.value));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
next();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
9
28
|
function trailingSlashMiddleware(options) {
|
|
10
|
-
|
|
11
|
-
const trailingSlash = (_a = options.rootConfig.server) == null ? void 0 : _a.trailingSlash;
|
|
29
|
+
const trailingSlash = options.rootConfig.server?.trailingSlash;
|
|
12
30
|
return (req, res, next) => {
|
|
13
31
|
if (trailingSlash === true && !path.extname(req.path) && !req.path.endsWith("/")) {
|
|
14
32
|
const redirectPath = `${req.path}/`;
|
|
@@ -46,8 +64,8 @@ function removeTrailingSlashes(urlPath) {
|
|
|
46
64
|
import busboy from "busboy";
|
|
47
65
|
var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
48
66
|
function multipartMiddleware(options) {
|
|
49
|
-
const maxFileSize =
|
|
50
|
-
|
|
67
|
+
const maxFileSize = options?.maxFileSize || DEFAULT_MAX_FILE_SIZE;
|
|
68
|
+
const handler = (req, res, next) => {
|
|
51
69
|
const contentType = String(req.headers["content-type"] || "");
|
|
52
70
|
if (req.method === "POST" && contentType.startsWith("multipart/form-data")) {
|
|
53
71
|
const parser = busboy({ headers: req.headers });
|
|
@@ -100,13 +118,14 @@ function multipartMiddleware(options) {
|
|
|
100
118
|
next();
|
|
101
119
|
}
|
|
102
120
|
};
|
|
121
|
+
return handler;
|
|
103
122
|
}
|
|
104
123
|
|
|
105
124
|
// src/middleware/session.ts
|
|
106
125
|
var SESSION_COOKIE = "__session";
|
|
107
126
|
var DEFAULT_MAX_AGE = 60 * 60 * 24 * 5 * 1e3;
|
|
108
127
|
function sessionMiddleware(options) {
|
|
109
|
-
const maxAge =
|
|
128
|
+
const maxAge = options?.maxAge || DEFAULT_MAX_AGE;
|
|
110
129
|
return (req, res, next) => {
|
|
111
130
|
const cookieValue = String(req.signedCookies[SESSION_COOKIE] || "");
|
|
112
131
|
const session = Session.fromCookieValue(cookieValue);
|
|
@@ -115,7 +134,7 @@ function sessionMiddleware(options) {
|
|
|
115
134
|
res.saveSession = (saveSessionOptions) => {
|
|
116
135
|
const secureCookie = Boolean(process.env.NODE_ENV !== "development");
|
|
117
136
|
const cookieValue2 = session.toString();
|
|
118
|
-
const sameSite =
|
|
137
|
+
const sameSite = saveSessionOptions?.sameSite || "strict";
|
|
119
138
|
res.cookie(SESSION_COOKIE, cookieValue2, {
|
|
120
139
|
maxAge,
|
|
121
140
|
httpOnly: true,
|
|
@@ -174,10 +193,11 @@ function base64Decode(str) {
|
|
|
174
193
|
|
|
175
194
|
export {
|
|
176
195
|
rootProjectMiddleware,
|
|
196
|
+
headersMiddleware,
|
|
177
197
|
trailingSlashMiddleware,
|
|
178
198
|
multipartMiddleware,
|
|
179
199
|
SESSION_COOKIE,
|
|
180
200
|
sessionMiddleware,
|
|
181
201
|
Session
|
|
182
202
|
};
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-TZAHHHA4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/common.ts","../src/middleware/multipart.ts","../src/middleware/session.ts"],"sourcesContent":["import path from 'node:path';\nimport micromatch from 'micromatch';\nimport {RootConfig} from '../core/config';\nimport {Request, Response, NextFunction} from '../core/types';\n\n/**\n * Middleware that injects the root.js project config into the request context.\n */\nexport function rootProjectMiddleware(options: {rootConfig: RootConfig}) {\n return (req: Request, _: Response, next: NextFunction) => {\n req.rootConfig = options.rootConfig;\n next();\n };\n}\n\n/**\n * Middleware that injects HTTP headers from the `server.headers` config in\n * root.config.ts.\n */\nexport function headersMiddleware(options: {rootConfig: RootConfig}) {\n const headersUserConfig = options.rootConfig.server?.headers || [];\n // Filter header config values that are invalid.\n const headersConfig = headersUserConfig.filter((headerConfig) => {\n return (\n headerConfig.source &&\n headerConfig.headers &&\n headerConfig.headers.length > 0\n );\n });\n return (req: Request, res: Response, next: NextFunction) => {\n headersConfig.forEach((headerConfig) => {\n if (micromatch.isMatch(req.path, headerConfig.source)) {\n headerConfig.headers.forEach((header) => {\n if (header.key) {\n res.setHeader(String(header.key), String(header.value));\n }\n });\n }\n });\n next();\n };\n}\n\n/**\n * Trailing slash middleware. Handles trailing slash redirects (preserving any\n * query params) using the `server.trailingSlash` config in root.config.ts.\n */\nexport function trailingSlashMiddleware(options: {rootConfig: RootConfig}) {\n const trailingSlash = options.rootConfig.server?.trailingSlash;\n\n return (req: Request, res: Response, next: NextFunction) => {\n // If `trailingSlash: false`, force a trailing slash in the URL.\n if (\n trailingSlash === true &&\n !path.extname(req.path) &&\n !req.path.endsWith('/')\n ) {\n const redirectPath = `${req.path}/`;\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n // If `trailingSlash: false`, remove any trailing slash from the URL.\n if (\n trailingSlash === false &&\n !path.extname(req.path) &&\n req.path !== '/' &&\n req.path.endsWith('/')\n ) {\n const redirectPath = removeTrailingSlashes(req.path);\n redirectWithQuery(req, res, 301, redirectPath);\n return;\n }\n\n next();\n };\n}\n\n/**\n * Issues an HTTP redirect, preserving any query params from the original req.\n */\nfunction redirectWithQuery(\n req: Request,\n res: Response,\n redirectCode: number,\n redirectPath: string\n) {\n const queryStr = getQueryStr(req);\n const redirectUrl = queryStr ? `${redirectPath}?${queryStr}` : redirectPath;\n res.redirect(redirectCode, redirectUrl);\n}\n\n/**\n * Returns the query string for a request, or empty string if no query.\n */\nfunction getQueryStr(req: Request): string {\n const qIndex = req.originalUrl.indexOf('?');\n if (qIndex === -1) {\n return '';\n }\n return req.originalUrl.slice(qIndex + 1);\n}\n\n/**\n * Removes trailing slashes from a URL path.\n * Note: A path with only slashes (e.g. `///`) returns `/`.\n */\nfunction removeTrailingSlashes(urlPath: string) {\n while (urlPath.endsWith('/') && urlPath !== '/') {\n urlPath = urlPath.slice(0, -1);\n }\n return urlPath;\n}\n","import busboy from 'busboy';\nimport {RequestHandler} from 'express';\nimport {Request, Response, NextFunction, MultipartFile} from '../core/types';\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB\n\n/**\n * Middleware for parsing multipart file uploads that's compatible with the dev\n * server and Firebase Functions.\n *\n * Context:\n * https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase\n */\nexport function multipartMiddleware(options?: {\n maxFileSize?: number;\n}): RequestHandler {\n const maxFileSize = options?.maxFileSize || DEFAULT_MAX_FILE_SIZE;\n const handler: any = (req: Request, res: Response, next: NextFunction) => {\n const contentType = String(req.headers['content-type'] || '');\n if (\n req.method === 'POST' &&\n contentType.startsWith('multipart/form-data')\n ) {\n const parser = busboy({headers: req.headers});\n\n // Data storage for fields and files\n const fields: {[fieldname: string]: string} = {};\n const files: {[name: string]: MultipartFile} = {};\n\n // Handle field data.\n parser.on('field', (fieldname: string, val: any) => {\n fields[fieldname] = val;\n });\n\n // Handle file data. Files are saved to an in-memory buffer.\n parser.on(\n 'file',\n (\n fieldname: string,\n file: any,\n meta: {\n filename: string;\n encoding: string;\n mimeType: string;\n }\n ) => {\n const {filename, encoding, mimeType: mimetype} = meta;\n const fileChunks: Uint8Array[] = [];\n let totalBytesRead = 0;\n\n file.on('data', (chunk: Uint8Array) => {\n totalBytesRead += chunk.length;\n\n if (totalBytesRead > maxFileSize) {\n // File size exceeds the limit, stop reading.\n console.error(`File size exceeds the limit: ${fieldname}.`);\n file.removeAllListeners('data');\n // Consume and discard remaining data.\n file.resume();\n } else {\n fileChunks.push(chunk);\n }\n });\n\n file.on('end', () => {\n if (totalBytesRead <= maxFileSize) {\n const buffer = Buffer.concat(fileChunks);\n\n files[fieldname] = {\n fieldname,\n buffer,\n originalName: filename,\n encoding,\n mimetype,\n };\n }\n });\n }\n );\n\n // Update `req.body` and `req.files`.\n parser.on('finish', () => {\n req.body = fields;\n req.files = files;\n next();\n });\n\n // Pipe the request to Busboy. On Firebase Functions, `rawBody` contains\n // the multipart request. Otherwise the express request can be piped to\n // the Busboy parser.\n if (req.rawBody) {\n parser.end(req.rawBody);\n } else {\n req.pipe(parser);\n }\n } else {\n next();\n }\n };\n return handler;\n}\n","import {NextFunction, Request, Response} from '../core/types';\n\nexport const SESSION_COOKIE = '__session';\n\n// 5 days.\nconst DEFAULT_MAX_AGE = 60 * 60 * 24 * 5 * 1000;\n\nexport interface SessionMiddlewareOptions {\n maxAge?: number;\n}\n\nexport interface SaveSessionOptions {\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\n/**\n * Middleware for storing session data stored in an http cookie called\n * `__session`. This cookie is compatible with Firebase Hosting:\n * https://firebase.google.com/docs/hosting/manage-cache#using_cookies\n */\nexport function sessionMiddleware(options?: SessionMiddlewareOptions) {\n const maxAge = options?.maxAge || DEFAULT_MAX_AGE;\n return (req: Request, res: Response, next: NextFunction) => {\n const cookieValue = String(req.signedCookies[SESSION_COOKIE] || '');\n const session = Session.fromCookieValue(cookieValue);\n req.session = session;\n res.session = session;\n res.saveSession = (saveSessionOptions?: SaveSessionOptions) => {\n // \"secure\" cookies require https, so disable \"secure\" when in development.\n const secureCookie = Boolean(process.env.NODE_ENV !== 'development');\n const cookieValue = session.toString();\n const sameSite = saveSessionOptions?.sameSite || 'strict';\n res.cookie(SESSION_COOKIE, cookieValue, {\n maxAge: maxAge,\n httpOnly: true,\n secure: secureCookie,\n signed: true,\n sameSite: sameSite,\n });\n };\n req.hooks.add('beforeRender', () => {\n res.saveSession();\n });\n next();\n };\n}\n\nexport class Session {\n private data: Record<string, string> = {};\n\n constructor(data?: Record<string, string>) {\n this.data = data || {};\n }\n\n static fromCookieValue(cookieValue: string) {\n const data: Record<string, string> = {};\n if (cookieValue.startsWith('b64:')) {\n try {\n const encodedStr = cookieValue.slice(4);\n const params = new URLSearchParams(base64Decode(encodedStr));\n for (const [key, value] of params.entries()) {\n data[key] = value;\n }\n } catch (err) {\n console.warn('failed to parse session cookie:', err);\n return new Session();\n }\n }\n return new Session(data);\n }\n\n getItem(key: string): string | null {\n return this.data[key] ?? null;\n }\n\n setItem(key: string, value: string) {\n this.data[key] = value;\n }\n\n removeItem(key: string) {\n delete this.data[key];\n }\n\n toString(): string {\n const params = new URLSearchParams(this.data);\n return `b64:${base64Encode(params.toString())}`;\n }\n}\n\nfunction base64Encode(str: string): string {\n return Buffer.from(str, 'utf-8').toString('base64');\n}\n\nfunction base64Decode(str: string): string {\n return Buffer.from(str, 'base64').toString('utf-8');\n}\n"],"mappings":"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","names":["cookieValue"]}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
// src/node/load-config.ts
|
|
6
6
|
import path2 from "node:path";
|
|
7
7
|
import { bundleRequire } from "bundle-require";
|
|
8
|
+
import { build } from "esbuild";
|
|
8
9
|
|
|
9
10
|
// src/utils/fsutils.ts
|
|
10
11
|
import { promises as fs } from "node:fs";
|
|
@@ -26,6 +27,12 @@ async function makeDir(dirpath) {
|
|
|
26
27
|
await fs.mkdir(dirpath, { recursive: true });
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
async function copyDir(srcdir, dstdir) {
|
|
31
|
+
if (!fsExtra.existsSync(srcdir)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
fsExtra.copySync(srcdir, dstdir, { overwrite: true });
|
|
35
|
+
}
|
|
29
36
|
async function copyGlob(pattern, srcdir, dstdir) {
|
|
30
37
|
const files = await glob(pattern, { cwd: srcdir });
|
|
31
38
|
if (files.length > 0) {
|
|
@@ -42,6 +49,10 @@ async function loadJson(filepath) {
|
|
|
42
49
|
const content = await fs.readFile(filepath, "utf-8");
|
|
43
50
|
return JSON.parse(content);
|
|
44
51
|
}
|
|
52
|
+
async function writeJson(filepath, data) {
|
|
53
|
+
const content = JSON.stringify(data, null, 2);
|
|
54
|
+
await writeFile(filepath, content);
|
|
55
|
+
}
|
|
45
56
|
async function isDirectory(dirpath) {
|
|
46
57
|
return fs.stat(dirpath).then((fsStat) => {
|
|
47
58
|
return fsStat.isDirectory();
|
|
@@ -89,17 +100,62 @@ async function loadRootConfig(rootDir, options) {
|
|
|
89
100
|
}
|
|
90
101
|
return Object.assign({}, config, { rootDir });
|
|
91
102
|
}
|
|
103
|
+
async function bundleRootConfig(rootDir, outPath) {
|
|
104
|
+
const configPath = path2.resolve(rootDir, "root.config.ts");
|
|
105
|
+
const exists = await fileExists(configPath);
|
|
106
|
+
if (!exists) {
|
|
107
|
+
throw new Error(`${configPath} does not exist`);
|
|
108
|
+
}
|
|
109
|
+
await build({
|
|
110
|
+
entryPoints: [configPath],
|
|
111
|
+
bundle: true,
|
|
112
|
+
minify: true,
|
|
113
|
+
platform: "node",
|
|
114
|
+
outfile: outPath,
|
|
115
|
+
sourcemap: "inline",
|
|
116
|
+
metafile: true,
|
|
117
|
+
format: "esm",
|
|
118
|
+
plugins: [
|
|
119
|
+
{
|
|
120
|
+
name: "externalize-deps",
|
|
121
|
+
setup(build2) {
|
|
122
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
123
|
+
const id = args.path;
|
|
124
|
+
if (id[0] !== "@" && id[0] !== "." && !path2.isAbsolute(id)) {
|
|
125
|
+
return {
|
|
126
|
+
external: true
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function loadBundledConfig(rootDir, options) {
|
|
137
|
+
const configPath = path2.resolve(rootDir, "dist/root.config.js");
|
|
138
|
+
const exists = await fileExists(configPath);
|
|
139
|
+
if (!exists) {
|
|
140
|
+
throw new Error(`${configPath} does not exist`);
|
|
141
|
+
}
|
|
142
|
+
const module = await import(configPath);
|
|
143
|
+
let config = module.default || {};
|
|
144
|
+
if (typeof config === "function") {
|
|
145
|
+
config = await config(options) || {};
|
|
146
|
+
}
|
|
147
|
+
return Object.assign({}, config, { rootDir });
|
|
148
|
+
}
|
|
92
149
|
|
|
93
150
|
// src/node/vite.ts
|
|
94
151
|
import { createServer } from "vite";
|
|
95
152
|
async function createViteServer(rootConfig, options) {
|
|
96
|
-
var _a, _b, _c;
|
|
97
153
|
const rootDir = rootConfig.rootDir;
|
|
98
154
|
const viteConfig = rootConfig.vite || {};
|
|
99
|
-
let hmrOptions =
|
|
100
|
-
if (
|
|
155
|
+
let hmrOptions = viteConfig.server?.hmr;
|
|
156
|
+
if (options?.hmr === false) {
|
|
101
157
|
hmrOptions = false;
|
|
102
|
-
} else if (typeof hmrOptions === "undefined" &&
|
|
158
|
+
} else if (typeof hmrOptions === "undefined" && options?.port) {
|
|
103
159
|
hmrOptions = { port: options.port + 10 };
|
|
104
160
|
}
|
|
105
161
|
const viteServer = await createServer({
|
|
@@ -117,16 +173,29 @@ async function createViteServer(rootConfig, options) {
|
|
|
117
173
|
},
|
|
118
174
|
appType: "custom",
|
|
119
175
|
optimizeDeps: {
|
|
176
|
+
// As of vite v5 / esbuild v19, experimentalDecorators need to be
|
|
177
|
+
// explicitly set, and for some reason this option isn't read from the
|
|
178
|
+
// project's tsconfig.json file by default.
|
|
179
|
+
// See: https://vitejs.dev/blog/announcing-vite5
|
|
180
|
+
esbuildOptions: {
|
|
181
|
+
tsconfigRaw: {
|
|
182
|
+
compilerOptions: {
|
|
183
|
+
target: "esnext",
|
|
184
|
+
experimentalDecorators: true,
|
|
185
|
+
useDefineForClassFields: false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
120
189
|
...viteConfig.optimizeDeps || {},
|
|
121
190
|
include: [
|
|
122
|
-
...
|
|
123
|
-
...
|
|
191
|
+
...options?.optimizeDeps || [],
|
|
192
|
+
...viteConfig.optimizeDeps?.include || []
|
|
124
193
|
],
|
|
125
|
-
extensions: [...
|
|
194
|
+
extensions: [...viteConfig.optimizeDeps?.extensions || [], ".tsx"]
|
|
126
195
|
},
|
|
127
196
|
ssr: {
|
|
128
197
|
...viteConfig.ssr || {},
|
|
129
|
-
noExternal: ["@blinkk/root"]
|
|
198
|
+
noExternal: ["@blinkk/root", "@blinkk/root-cms/richtext"]
|
|
130
199
|
},
|
|
131
200
|
esbuild: {
|
|
132
201
|
...viteConfig.esbuild || {},
|
|
@@ -151,15 +220,19 @@ export {
|
|
|
151
220
|
isJsFile,
|
|
152
221
|
writeFile,
|
|
153
222
|
makeDir,
|
|
223
|
+
copyDir,
|
|
154
224
|
copyGlob,
|
|
155
225
|
rmDir,
|
|
156
226
|
loadJson,
|
|
227
|
+
writeJson,
|
|
157
228
|
isDirectory,
|
|
158
229
|
fileExists,
|
|
159
230
|
dirExists,
|
|
160
231
|
directoryContains,
|
|
161
232
|
loadRootConfig,
|
|
233
|
+
bundleRootConfig,
|
|
234
|
+
loadBundledConfig,
|
|
162
235
|
createViteServer,
|
|
163
236
|
viteSsrLoadModule
|
|
164
237
|
};
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
238
|
+
//# sourceMappingURL=chunk-UYHD775U.js.map
|