jeasx 2.10.7 → 3.0.1
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/CHANGELOG.md +1485 -0
- package/README.md +31 -2
- package/build.js +37 -14
- package/cli.js +7 -1
- package/env.js +7 -1
- package/package.json +5 -5
- package/serverless.js +120 -105
- package/serverless.js.map +2 -2
- package/serverless.ts +216 -153
- package/start.js +13 -1
package/README.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Jeasx - the ease of JSX with the power of SSR
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/jeasx)
|
|
4
|
+
[](LICENSE.txt)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Jeasx is a server-side rendering framework for Node.js. You write pages and
|
|
7
|
+
API endpoints as async JSX functions, and Jeasx renders them on the server and
|
|
8
|
+
streams the HTML to the browser. Routing comes from your file structure.
|
|
9
|
+
|
|
10
|
+
It is built on [Fastify](https://fastify.dev), [esbuild](https://esbuild.github.io)
|
|
11
|
+
and [jsx-async-runtime](https://github.com/jeasx/jsx-async-runtime).
|
|
12
|
+
|
|
13
|
+
This is the Jeasx core repository. For guides, examples and the project
|
|
14
|
+
template, visit <https://www.jeasx.dev>. Live examples are at
|
|
15
|
+
<https://expo.jeasx.dev>.
|
|
16
|
+
|
|
17
|
+
## Development
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install
|
|
21
|
+
npm run build # compiles serverless.ts to serverless.js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`serverless.js` and `serverless.js.map` are committed, so rebuild and commit
|
|
25
|
+
them together after changing `serverless.ts`.
|
|
26
|
+
|
|
27
|
+
To try out changes in a full app, use
|
|
28
|
+
[jeasx-develop](https://github.com/jeasx/jeasx-develop), an ejected version of
|
|
29
|
+
[Jeasx Expo](https://expo.jeasx.dev) that runs the framework code directly
|
|
30
|
+
in the project, so it's easy to change and test.
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
[MIT](LICENSE.txt) © Maik Jablonski
|
package/build.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as esbuild from "esbuild";
|
|
2
|
-
import {
|
|
3
|
-
import { join, sep } from "node:path";
|
|
2
|
+
import { readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
import env from "./env.js";
|
|
5
6
|
|
|
6
7
|
env();
|
|
7
8
|
|
|
8
9
|
const CWD = process.cwd();
|
|
9
|
-
const CONFIG = (await import(
|
|
10
|
+
const CONFIG = (await import(pathToFileURL(join(CWD, "jeasx.config.js")).href)).default;
|
|
10
11
|
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
11
12
|
|
|
12
13
|
/** @type {esbuild.BuildOptions} */
|
|
@@ -14,7 +15,6 @@ const SERVER_OPTIONS = {
|
|
|
14
15
|
entryPoints: ["src/**/[*].*"],
|
|
15
16
|
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
16
17
|
logLevel: "info",
|
|
17
|
-
color: true,
|
|
18
18
|
bundle: true,
|
|
19
19
|
outdir: "dist",
|
|
20
20
|
publicPath: "/",
|
|
@@ -22,7 +22,8 @@ const SERVER_OPTIONS = {
|
|
|
22
22
|
format: "esm",
|
|
23
23
|
platform: "neutral",
|
|
24
24
|
packages: "external",
|
|
25
|
-
|
|
25
|
+
// Options may be async, so build-only plugins can be imported lazily (see jeasx.config.js).
|
|
26
|
+
...(await CONFIG.ESBUILD_SERVER_OPTIONS?.()),
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/** @type {esbuild.BuildOptions} */
|
|
@@ -30,7 +31,6 @@ const BROWSER_OPTIONS = {
|
|
|
30
31
|
entryPoints: ["src/**/index.*"],
|
|
31
32
|
minify: !NODE_ENV_IS_DEVELOPMENT,
|
|
32
33
|
logLevel: "info",
|
|
33
|
-
color: true,
|
|
34
34
|
bundle: true,
|
|
35
35
|
outdir: "dist",
|
|
36
36
|
publicPath: "/",
|
|
@@ -38,12 +38,16 @@ const BROWSER_OPTIONS = {
|
|
|
38
38
|
format: "esm",
|
|
39
39
|
platform: "browser",
|
|
40
40
|
packages: "bundle",
|
|
41
|
-
...CONFIG.ESBUILD_BROWSER_OPTIONS?.(),
|
|
41
|
+
...(await CONFIG.ESBUILD_BROWSER_OPTIONS?.()),
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
for (const options of [SERVER_OPTIONS, BROWSER_OPTIONS]) {
|
|
45
45
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
46
|
-
|
|
46
|
+
const context = await esbuild.context(options);
|
|
47
|
+
// watch() doesn't wait for the first build, so the server would start with an empty "dist".
|
|
48
|
+
// Build errors are already logged and fixed while watching, so don't stop on them.
|
|
49
|
+
await context.rebuild().catch(() => {});
|
|
50
|
+
await context.watch();
|
|
47
51
|
} else {
|
|
48
52
|
await esbuild.build(options);
|
|
49
53
|
}
|
|
@@ -56,23 +60,42 @@ if (!NODE_ENV_IS_DEVELOPMENT) {
|
|
|
56
60
|
/** @type {Record<string,string>} */
|
|
57
61
|
const files = {};
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
// Unlike glob, readdir includes dotfiles (e.g. ".well-known"), so the "dotfiles"
|
|
64
|
+
// option of FASTIFY_SEND_OPTIONS decides about them like in development.
|
|
65
|
+
// "dist" comes first: if a file exists in both directories, "dist" wins.
|
|
66
|
+
// Only regular files are collected; symbolic links are skipped (unlike in development).
|
|
67
|
+
const entries = [];
|
|
68
|
+
for (const directory of ["dist", "public"]) {
|
|
69
|
+
const dirents = await readdir(join(CWD, directory), {
|
|
70
|
+
recursive: true,
|
|
71
|
+
withFileTypes: true,
|
|
72
|
+
}).catch(() => []);
|
|
73
|
+
for (const dirent of dirents) {
|
|
74
|
+
const path = join(dirent.parentPath, dirent.name);
|
|
75
|
+
if (dirent.isFile()) {
|
|
76
|
+
entries.push(relative(CWD, path));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
60
82
|
const path = entry.split(sep).join("/");
|
|
61
83
|
|
|
62
84
|
// Handle server routes.
|
|
63
|
-
if (/^dist\/.*\[
|
|
85
|
+
if (/^dist\/.*\[[^/]+\]\.js$/.test(path)) {
|
|
64
86
|
routes[path.slice("dist".length, -".js".length)] = path;
|
|
65
87
|
continue;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
// Skip
|
|
69
|
-
if (/^dist\/.*\[
|
|
90
|
+
// Skip all other outputs of server routes, e.g. sourcemaps or CSS of components.
|
|
91
|
+
if (/^dist\/.*\[[^/]+\]\.[^/]+$/.test(path)) {
|
|
70
92
|
continue;
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
// Treat all other entries as static files.
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
const key = path.slice(path.indexOf("/"));
|
|
97
|
+
if (!(path.startsWith("public/") && key in files)) {
|
|
98
|
+
files[key] = path;
|
|
76
99
|
}
|
|
77
100
|
}
|
|
78
101
|
|
package/cli.js
CHANGED
|
@@ -7,20 +7,26 @@ switch (process.argv[2]) {
|
|
|
7
7
|
break;
|
|
8
8
|
|
|
9
9
|
case "build":
|
|
10
|
+
await clean();
|
|
10
11
|
await import("./build.js");
|
|
11
12
|
break;
|
|
12
13
|
|
|
13
14
|
case "dev":
|
|
14
15
|
process.env.NODE_ENV = "development";
|
|
16
|
+
await clean();
|
|
15
17
|
await import("./build.js");
|
|
16
18
|
await import("./start.js");
|
|
17
19
|
break;
|
|
18
20
|
|
|
19
21
|
case "clean":
|
|
20
|
-
await
|
|
22
|
+
await clean();
|
|
21
23
|
break;
|
|
22
24
|
|
|
23
25
|
default:
|
|
24
26
|
console.info(`💡 Usage: jeasx [start|build|dev|clean]`);
|
|
25
27
|
process.exit(1);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
async function clean() {
|
|
31
|
+
await fs.rm("dist", { recursive: true, force: true, maxRetries: 3 });
|
|
32
|
+
}
|
package/env.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Loads environment variables from .env files into process.env.
|
|
5
|
+
* Files are read in the order below. A variable that is already set,
|
|
6
|
+
* by the environment or by an earlier file, is never overwritten:
|
|
5
7
|
*
|
|
6
8
|
* 1. .env.<NODE_ENV>.local
|
|
7
9
|
* 2. .env.<NODE_ENV>
|
|
8
10
|
* 3. .env.local
|
|
9
11
|
* 4. .env
|
|
10
12
|
* 5. .env.defaults
|
|
13
|
+
*
|
|
14
|
+
* NODE_ENV itself must come from the environment (e.g. set by "jeasx dev"):
|
|
15
|
+
* the files above are chosen before any of them is read, so a NODE_ENV
|
|
16
|
+
* set in a file doesn't select .env.<NODE_ENV>(.local).
|
|
11
17
|
*/
|
|
12
18
|
export default function env() {
|
|
13
19
|
if (typeof process.loadEnvFile === "function") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeasx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Jeasx - the ease of JSX with the power of SSR",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"async",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"build": "esbuild --platform=node --format=esm --sourcemap=linked --outdir=. serverless.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@fastify/send": "4.2.
|
|
33
|
-
"@types/node": "26.6.
|
|
32
|
+
"@fastify/send": "4.2.1",
|
|
33
|
+
"@types/node": "26.6.6",
|
|
34
34
|
"esbuild": "0.28.2",
|
|
35
|
-
"fastify": "5.12.
|
|
36
|
-
"jsx-async-runtime": "
|
|
35
|
+
"fastify": "5.12.8",
|
|
36
|
+
"jsx-async-runtime": "3.0.1"
|
|
37
37
|
},
|
|
38
38
|
"allowScripts": {
|
|
39
39
|
"esbuild": true
|
package/serverless.js
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
import fastifySend from "@fastify/send";
|
|
2
2
|
import fastify from "fastify";
|
|
3
|
-
import {
|
|
3
|
+
import { isJsxNode, jsxToStream } from "jsx-async-runtime";
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
import env from "./env.js";
|
|
8
9
|
env();
|
|
9
10
|
const CWD = process.cwd();
|
|
10
|
-
const CONFIG = (await import(
|
|
11
|
+
const CONFIG = (await import(pathToFileURL(join(CWD, "jeasx.config.js")).href)).default;
|
|
11
12
|
const NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === "development";
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const METADATA = NODE_ENV_IS_DEVELOPMENT ? { routes: {}, files: {} } : (await import(pathToFileURL(join(CWD, "dist", "[--metadata--].js")).href)).default;
|
|
14
|
+
const MODULE_BY_ROUTE = Object.assign(
|
|
15
|
+
/* @__PURE__ */ Object.create(null),
|
|
16
|
+
METADATA.routes
|
|
17
|
+
);
|
|
18
|
+
const FILE_BY_PATH = Object.assign(/* @__PURE__ */ Object.create(null), METADATA.files);
|
|
19
|
+
const FASTIFY_SEND_OPTIONS = {
|
|
20
|
+
...await CONFIG.FASTIFY_SEND_OPTIONS?.(),
|
|
21
|
+
root: CWD
|
|
22
|
+
};
|
|
17
23
|
const FASTIFY_SERVER = CONFIG.FASTIFY_SERVER ?? ((fastify2) => fastify2);
|
|
18
|
-
var serverless_default = FASTIFY_SERVER(
|
|
19
|
-
fastify(CONFIG.FASTIFY_SERVER_OPTIONS?.()).decorateRequest("route", "").decorateRequest("path", "").decorateReply("file", void 0).addHook("onRequest", async (request) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
24
|
+
var serverless_default = await FASTIFY_SERVER(
|
|
25
|
+
fastify(await CONFIG.FASTIFY_SERVER_OPTIONS?.()).decorateRequest("route", "").decorateRequest("path", "").decorateReply("file", void 0).addHook("onRequest", async (request, reply) => {
|
|
26
|
+
const path = decodePath(request.url);
|
|
27
|
+
if (path === void 0) {
|
|
28
|
+
reply.code(400).send();
|
|
29
|
+
return reply;
|
|
30
|
+
}
|
|
31
|
+
request.path = path;
|
|
22
32
|
}).all("*", async (request, reply) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
return payload;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
request.log.error(error);
|
|
32
|
-
throw error;
|
|
33
|
-
} finally {
|
|
34
|
-
const stream = reply.file?.stream;
|
|
35
|
-
if (stream && stream !== payload && stream.readableFlowing === null) {
|
|
36
|
-
stream.destroy();
|
|
37
|
-
}
|
|
33
|
+
reply.raw.once("close", () => reply.file?.stream.destroy());
|
|
34
|
+
const payload = await handler(request, reply);
|
|
35
|
+
if (reply.getHeader("content-type") === void 0 && (typeof payload === "string" || payload instanceof ReadableStream || // Static files bring their own content type (or none, e.g. for 304).
|
|
36
|
+
payload instanceof Readable && payload !== reply.file?.stream || Buffer.isBuffer(payload))) {
|
|
37
|
+
reply.type("text/html; charset=utf-8");
|
|
38
38
|
}
|
|
39
|
+
return payload;
|
|
39
40
|
})
|
|
40
41
|
);
|
|
41
42
|
const STATIC_FILE_ROUTE = /* @__PURE__ */ Symbol("static-file-route");
|
|
@@ -43,83 +44,76 @@ async function handler(request, reply) {
|
|
|
43
44
|
let response;
|
|
44
45
|
const context = {};
|
|
45
46
|
const props = { request, reply };
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
let module = MODULE_BY_ROUTE[route];
|
|
59
|
-
if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
|
|
60
|
-
continue;
|
|
47
|
+
reply.file = await tryFile(request);
|
|
48
|
+
for (const route of generateRoutes(request.path)) {
|
|
49
|
+
if (route === STATIC_FILE_ROUTE) {
|
|
50
|
+
if (reply.file) {
|
|
51
|
+
reply.status(reply.file.statusCode);
|
|
52
|
+
reply.headers(reply.file.headers);
|
|
53
|
+
response = reply.file.stream;
|
|
54
|
+
break;
|
|
61
55
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
case "ENOENT":
|
|
76
|
-
case "ENOTDIR":
|
|
77
|
-
case "ERR_MODULE_NOT_FOUND":
|
|
78
|
-
continue;
|
|
79
|
-
default:
|
|
80
|
-
throw e;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let module = MODULE_BY_ROUTE[route];
|
|
59
|
+
if (module === void 0 && !NODE_ENV_IS_DEVELOPMENT) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (typeof module === "string") {
|
|
64
|
+
module = MODULE_BY_ROUTE[route] = await import(pathToFileURL(join(CWD, module)).href);
|
|
65
|
+
} else if (module === void 0 && NODE_ENV_IS_DEVELOPMENT) {
|
|
66
|
+
const modulePath = join(CWD, "dist", `${route}.js`);
|
|
67
|
+
if (typeof require === "function" && require.cache[modulePath]) {
|
|
68
|
+
delete require.cache[modulePath];
|
|
81
69
|
}
|
|
70
|
+
const mtime = (await stat(modulePath)).mtime.getTime();
|
|
71
|
+
module = await import(`${pathToFileURL(modulePath).href}?${mtime}`);
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
module.default
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
if (reply.sent) {
|
|
94
|
-
return;
|
|
95
|
-
} else if (route.endsWith("/[404]")) {
|
|
96
|
-
if (reply.statusCode === 200 && !request.path.endsWith("/404")) {
|
|
97
|
-
reply.status(404);
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
} else if (typeof response === "string" || response instanceof Response || response instanceof Readable || response instanceof ReadableStream || Buffer.isBuffer(response) || isJSX(response)) {
|
|
101
|
-
break;
|
|
102
|
-
} else if (typeof response === "object" && route.endsWith("/[...guard]")) {
|
|
103
|
-
Object.assign(props, response);
|
|
104
|
-
continue;
|
|
105
|
-
} else if (response === void 0 || reply.statusCode === 404) {
|
|
106
|
-
continue;
|
|
107
|
-
} else {
|
|
108
|
-
break;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
switch (e?.code) {
|
|
75
|
+
case "ENOENT":
|
|
76
|
+
case "ENOTDIR":
|
|
77
|
+
continue;
|
|
78
|
+
default:
|
|
79
|
+
throw e;
|
|
109
80
|
}
|
|
110
81
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
82
|
+
request.route = route;
|
|
83
|
+
if (module && typeof module === "object") {
|
|
84
|
+
response = typeof module.default === "function" ? (
|
|
85
|
+
// Call functions with context as `this` and props as parameters,
|
|
86
|
+
await module.default.call(context, props)
|
|
87
|
+
) : (
|
|
88
|
+
// otherwise return default export.
|
|
89
|
+
module.default
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (reply.sent) {
|
|
93
|
+
return;
|
|
94
|
+
} else if (route.endsWith("/[404]")) {
|
|
95
|
+
if (reply.statusCode === 200 && !(route.slice(0, -"[404]".length) + "404" === request.path.replace(/\/$/, ""))) {
|
|
96
|
+
reply.status(404);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
} else if (typeof response === "string" || response instanceof Response || response instanceof Readable || response instanceof ReadableStream || Buffer.isBuffer(response) || isJsxNode(response)) {
|
|
100
|
+
break;
|
|
101
|
+
} else if (typeof response === "object" && route.endsWith("/[...guard]")) {
|
|
102
|
+
Object.assign(props, response);
|
|
103
|
+
response = void 0;
|
|
104
|
+
continue;
|
|
105
|
+
} else if (response == null) {
|
|
106
|
+
continue;
|
|
118
107
|
} else {
|
|
119
|
-
|
|
108
|
+
break;
|
|
120
109
|
}
|
|
121
110
|
}
|
|
111
|
+
if (response == null && reply.statusCode === 200) {
|
|
112
|
+
reply.status(404);
|
|
113
|
+
}
|
|
114
|
+
return await renderResponse(context, response);
|
|
122
115
|
}
|
|
116
|
+
const SPECIAL_SEGMENTS = /* @__PURE__ */ new Set(["index", "...guard", "...path", "--metadata--"]);
|
|
123
117
|
function* generateRoutes(path) {
|
|
124
118
|
const segments = [""];
|
|
125
119
|
let edgeSegment = "";
|
|
@@ -134,8 +128,9 @@ function* generateRoutes(path) {
|
|
|
134
128
|
}
|
|
135
129
|
yield STATIC_FILE_ROUTE;
|
|
136
130
|
const lastSlash = edgeSegment.lastIndexOf("/") + 1;
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const lastSegment = edgeSegment.substring(lastSlash);
|
|
132
|
+
if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {
|
|
133
|
+
yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;
|
|
139
134
|
}
|
|
140
135
|
yield `${edgeSegment}/[index]`;
|
|
141
136
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
@@ -145,28 +140,40 @@ function* generateRoutes(path) {
|
|
|
145
140
|
yield `${segments[i]}/[404]`;
|
|
146
141
|
}
|
|
147
142
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
143
|
+
function decodePath(url) {
|
|
144
|
+
try {
|
|
145
|
+
const index = url.indexOf("?");
|
|
146
|
+
const rawPath = index === -1 ? url : url.slice(0, index);
|
|
147
|
+
const path = decodeURIComponent(rawPath).replace(/[\\/]+/g, "/");
|
|
148
|
+
if (path.includes("\0") || /(^|\/)\.\.?(\/|$)/.test(path)) {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
return path;
|
|
152
|
+
} catch {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
150
155
|
}
|
|
151
156
|
async function renderResponse(context, response) {
|
|
152
|
-
const payload =
|
|
157
|
+
const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;
|
|
153
158
|
const responseHandler = context["responseHandler"];
|
|
154
159
|
return typeof responseHandler === "function" ? await responseHandler.call(context, payload) : payload;
|
|
155
160
|
}
|
|
156
161
|
async function tryFile(request) {
|
|
162
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
157
165
|
const file = FILE_BY_PATH[request.path];
|
|
158
166
|
if (file) {
|
|
159
|
-
return await
|
|
167
|
+
return await sendFile(request, file);
|
|
160
168
|
}
|
|
161
169
|
if (NODE_ENV_IS_DEVELOPMENT) {
|
|
162
170
|
for (const directory of ["dist", "public"]) {
|
|
171
|
+
if (directory === "dist" && /\[[^/]+\]\.[^/]*$/u.test(request.path)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
163
174
|
try {
|
|
164
175
|
if ((await stat(join(CWD, directory, request.path))).isFile()) {
|
|
165
|
-
return await
|
|
166
|
-
request.raw,
|
|
167
|
-
`${directory}${request.path}`,
|
|
168
|
-
FASTIFY_SEND_OPTIONS
|
|
169
|
-
);
|
|
176
|
+
return await sendFile(request, `${directory}${request.path}`);
|
|
170
177
|
}
|
|
171
178
|
} catch {
|
|
172
179
|
continue;
|
|
@@ -175,6 +182,14 @@ async function tryFile(request) {
|
|
|
175
182
|
}
|
|
176
183
|
return void 0;
|
|
177
184
|
}
|
|
185
|
+
async function sendFile(request, path) {
|
|
186
|
+
const result = await fastifySend(request.raw, encodeURI(path), FASTIFY_SEND_OPTIONS);
|
|
187
|
+
if (result.type === "error" && result.statusCode === 404) {
|
|
188
|
+
result.stream.destroy();
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
178
193
|
export {
|
|
179
194
|
serverless_default as default
|
|
180
195
|
};
|
package/serverless.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["serverless.ts"],
|
|
4
|
-
"sourcesContent": ["import fastifySend, { BaseSendResult, SendOptions } from \"@fastify/send\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { jsxToString } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(`file://${join(CWD, \"jeasx.config.js\")}`)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Map routes and files for non-development environments from metadata export.\n// Module paths are initialized at startup but overwritten\n// with resolved modules upon the first request.\nconst {\n routes: MODULE_BY_ROUTE,\n files: FILE_BY_PATH,\n}: {\n routes: Record<string, string | { default: Function }>;\n files: Record<string, string>;\n} = NODE_ENV_IS_DEVELOPMENT\n ? { routes: {}, files: {} }\n : (await import(`file://${join(CWD, \"dist\", \"[--metadata--].js\")}`)).default;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /** Path without query parameters */\n path: string;\n /** Path to resolved route handler */\n route: string;\n }\n\n interface FastifyReply {\n /** Populated when serving a static file; otherwise undefined. */\n file?: BaseSendResult;\n }\n}\n\nconst FASTIFY_SEND_OPTIONS = CONFIG.FASTIFY_SEND_OPTIONS?.() as SendOptions;\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance;\n\n// Create and export a Fastify instance\nexport default FASTIFY_SERVER(\n fastify(CONFIG.FASTIFY_SERVER_OPTIONS?.() as FastifyServerOptions)\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .decorateReply(\"file\", undefined)\n .addHook(\"onRequest\", async (request) => {\n // Extract path from url\n const index = request.url.indexOf(\"?\");\n request.path = index === -1 ? request.url : request.url.slice(0, index);\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n let payload: unknown;\n try {\n payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" || Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n } catch (error) {\n request.log.error(error);\n throw error;\n } finally {\n // Close file stream if it isn't sent and nobody started reading it,\n // otherwise its file handle leaks.\n const stream = reply.file?.stream;\n if (stream && stream !== payload && stream.readableFlowing === null) {\n stream.destroy();\n }\n }\n }),\n);\n\n// Used as marker for static file routes.\nconst STATIC_FILE_ROUTE = Symbol(\"static-file-route\");\n\n/**\n * Resolves route module based on the request path and execute it.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context: any = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n try {\n // Check for static file and store result for later processing.\n reply.file = await tryFile(request);\n\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Try to serve static file\n if (route === STATIC_FILE_ROUTE) {\n if (reply.file) {\n reply.status(reply.file.statusCode);\n reply.headers(reply.file.headers);\n response = reply.file.stream;\n break;\n }\n continue;\n }\n\n // Resolve module or path to module\n let module = MODULE_BY_ROUTE[route];\n\n // Skip processing if the route path was not initialized.\n if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {\n continue;\n }\n\n // Module was not loaded yet?\n try {\n if (typeof module === \"string\") {\n // Production: Load and cache module only via pre-calculated path.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `route` values.\n module = MODULE_BY_ROUTE[route] = await import(`file://${join(CWD, module)}`);\n } else if (module === undefined && NODE_ENV_IS_DEVELOPMENT) {\n // Only map module paths depending on `route` during development.\n const modulePath = join(CWD, \"dist\", `${route}.js`);\n if (typeof require === \"function\" && require.cache[modulePath]) {\n // Bun: Remove module from cache before importing\n // as query parameter for import is ignored.\n delete require.cache[modulePath];\n }\n // Use timestamp as query parameter to update modules.\n const mtime = (await stat(modulePath)).mtime.getTime();\n // Dynamic imports are restricted to development environments;\n // therefore, production-level path validation is not required here.\n module = await import(`file://${modulePath}?${mtime}`);\n }\n } catch (e) {\n switch ((e as any)?.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n case \"ERR_MODULE_NOT_FOUND\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n\n // Store current route in request.\n request.route = route;\n\n // Ensure module is a valid object before processing.\n if (module && typeof module === \"object\") {\n response =\n typeof module.default === \"function\"\n ? // Call functions with context as `this` and props as parameters,\n await module.default.call(context, props)\n : // otherwise return default export.\n module.default;\n }\n\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly.\n // If no status is defined, set status to 404 automatically.\n if (reply.statusCode === 200 && !request.path.endsWith(\"/404\")) {\n reply.status(404);\n }\n break;\n } else if (\n typeof response === \"string\" ||\n response instanceof Response ||\n response instanceof Readable ||\n response instanceof ReadableStream ||\n Buffer.isBuffer(response) ||\n isJSX(response)\n ) {\n break;\n } else if (typeof response === \"object\" && route.endsWith(\"/[...guard]\")) {\n // Add object entries from guard to props\n Object.assign(props, response);\n continue;\n } else if (response === undefined || reply.statusCode === 404) {\n continue;\n } else {\n break;\n }\n }\n return await renderResponse(context, response);\n } catch (error) {\n const errorHandler = context[\"errorHandler\"];\n if (typeof errorHandler === \"function\") {\n reply.status(500);\n response = await errorHandler.call(context, error);\n return await renderResponse(context, response);\n } else {\n throw error;\n }\n }\n}\n\n/**\n * Generates all possible routes based on the given input path.\n *\n * Example routes for \"/a/b/c\":\n *\n * [\n * \"/[...guard]\",\"/a/[...guard]\",\"/a/b/[...guard]\",\"/a/b/c/[...guard]\",\n * STATIC_FILE,\n * \"/a/b/[c]\",\"/a/b/c/[index]\",\n * \"/a/b/c/[...path]\",\"/a/b/[...path]\",\"/a/[...path]\",\"/[...path]\",\n * \"/a/b/c/[404]\",\"/a/b/[404]\",\"/a/[404]\",\"/[404]\"\n * ]\n */\nfunction* generateRoutes(path: string): Generator<string | typeof STATIC_FILE_ROUTE> {\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"\", \"/a\", \"/a/b/\", \"/a/b/c\"]\n const segments = [\"\"];\n let edgeSegment = \"\";\n for (const segment of path.split(\"/\")) {\n // Ignore redundant slashes.\n if (segment !== \"\") {\n edgeSegment += `/${segment}`;\n segments.push(edgeSegment);\n }\n }\n\n // [...guard]s are pushed from root to edge.\n for (let i = 0; i < segments.length; i++) {\n yield `${segments[i]}/[...guard]`;\n }\n\n // Append the marker for static files\n yield STATIC_FILE_ROUTE;\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n if (lastSlash > 0) {\n yield `${edgeSegment.substring(0, lastSlash)}[${edgeSegment.substring(lastSlash)}]`;\n }\n yield `${edgeSegment}/[index]`;\n\n // [...path]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[...path]`;\n }\n\n // [404]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[404]`;\n }\n}\n\n/**\n * Determines if a given object is a JSX element.\n */\nfunction isJSX(obj: unknown): boolean {\n return !!obj && typeof obj === \"object\" && \"type\" in obj && \"props\" in obj;\n}\n\n/**\n * Renders JSX to string and applies optional response handler.\n */\nasync function renderResponse(context: any, response: unknown) {\n const payload = isJSX(response)\n ? await jsxToString.call(context, response as JSX.Element)\n : response;\n\n // Post-process the payload with an optional response handler\n const responseHandler = context[\"responseHandler\"];\n return typeof responseHandler === \"function\"\n ? await responseHandler.call(context, payload)\n : payload;\n}\n\n/**\n * Returns stream and metadata for requested file.\n */\nasync function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefined> {\n // Production: Retrieve files only from pre-initialized mapping.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `request.path` values.\n const file = FILE_BY_PATH[request.path];\n if (file) {\n return await fastifySend(request.raw, file, FASTIFY_SEND_OPTIONS);\n }\n\n if (NODE_ENV_IS_DEVELOPMENT) {\n for (const directory of [\"dist\", \"public\"]) {\n try {\n if ((await stat(join(CWD, directory, request.path))).isFile()) {\n // Dynamic path loading is restricted to development environments;\n // therefore, production-level path validation is not required here.\n return await fastifySend(\n request.raw,\n `${directory}${request.path}`,\n FASTIFY_SEND_OPTIONS,\n );\n }\n } catch {\n continue;\n }\n }\n }\n\n return undefined;\n}\n"],
|
|
5
|
-
"mappings": "AAAA,OAAO,iBAAkD;AACzD,OAAO,aAKA;AACP,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import fastifySend, { BaseSendResult, SendOptions } from \"@fastify/send\";\nimport fastify, {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n FastifyServerOptions,\n} from \"fastify\";\nimport { isJsxNode, jsxToStream } from \"jsx-async-runtime\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pathToFileURL } from \"node:url\";\nimport env from \"./env.js\";\n\nenv();\n\nconst CWD = process.cwd();\nconst CONFIG = (await import(pathToFileURL(join(CWD, \"jeasx.config.js\")).href)).default;\nconst NODE_ENV_IS_DEVELOPMENT = process.env.NODE_ENV === \"development\";\n\n// Map routes and files for non-development environments from metadata export.\n// Module paths are initialized at startup but overwritten\n// with resolved modules upon the first request.\nconst METADATA: {\n routes: Record<string, string | { default: Function }>;\n files: Record<string, string>;\n} = NODE_ENV_IS_DEVELOPMENT\n ? { routes: {}, files: {} }\n : (await import(pathToFileURL(join(CWD, \"dist\", \"[--metadata--].js\")).href)).default;\n\n// Without a prototype, lookups never return inherited keys like \"constructor\".\nconst MODULE_BY_ROUTE = Object.assign(\n Object.create(null),\n METADATA.routes,\n) as typeof METADATA.routes;\nconst FILE_BY_PATH = Object.assign(Object.create(null), METADATA.files) as typeof METADATA.files;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /** Decoded path without query parameters */\n path: string;\n /** Path to resolved route handler */\n route: string;\n }\n\n interface FastifyReply {\n /** Populated when serving a static file; otherwise undefined. */\n file?: BaseSendResult;\n }\n}\n\n// All config options may be sync or async functions.\n\n// Files are passed relative to CWD, so the root must always be CWD.\nconst FASTIFY_SEND_OPTIONS: SendOptions = {\n ...((await CONFIG.FASTIFY_SEND_OPTIONS?.()) as SendOptions),\n root: CWD,\n};\n\n// Enhance Fastify server from userland\nconst FASTIFY_SERVER = (CONFIG.FASTIFY_SERVER ?? ((fastify) => fastify)) as (\n fastify: FastifyInstance,\n) => FastifyInstance | Promise<FastifyInstance>;\n\n// Create and export a Fastify instance.\n// A Fastify instance is thenable: awaiting it loads the plugins registered so far\n// and resolves to the instance itself, so further plugins and routes can still be added.\nexport default await FASTIFY_SERVER(\n fastify((await CONFIG.FASTIFY_SERVER_OPTIONS?.()) as FastifyServerOptions)\n .decorateRequest(\"route\", \"\")\n .decorateRequest(\"path\", \"\")\n .decorateReply(\"file\", undefined)\n .addHook(\"onRequest\", async (request, reply) => {\n const path = decodePath(request.url);\n if (path === undefined) {\n reply.code(400).send();\n return reply;\n }\n request.path = path;\n })\n .all(\"*\", async (request: FastifyRequest, reply: FastifyReply) => {\n // Close the file stream once the response is finished or aborted,\n // otherwise its file handle leaks if nobody reads it. Waiting for the\n // response also covers routes which send the stream via reply.send().\n reply.raw.once(\"close\", () => reply.file?.stream.destroy());\n\n const payload = await handler(request, reply);\n if (\n reply.getHeader(\"content-type\") === undefined &&\n (typeof payload === \"string\" ||\n payload instanceof ReadableStream ||\n // Static files bring their own content type (or none, e.g. for 304).\n (payload instanceof Readable && payload !== reply.file?.stream) ||\n Buffer.isBuffer(payload))\n ) {\n reply.type(\"text/html; charset=utf-8\");\n }\n return payload;\n }),\n);\n\n// Used as marker for static file routes.\nconst STATIC_FILE_ROUTE = Symbol(\"static-file-route\");\n\n/**\n * Resolves route modules based on the request path and executes them.\n */\nasync function handler(request: FastifyRequest, reply: FastifyReply) {\n let response: unknown;\n\n // Global context object for route handlers\n const context: any = {};\n\n // Default props for route handlers\n const props = { request, reply };\n\n // Check for static file and store result for later processing.\n reply.file = await tryFile(request);\n\n // Execute route handlers for current request\n for (const route of generateRoutes(request.path)) {\n // Try to serve static file\n if (route === STATIC_FILE_ROUTE) {\n if (reply.file) {\n reply.status(reply.file.statusCode);\n reply.headers(reply.file.headers);\n response = reply.file.stream;\n break;\n }\n continue;\n }\n\n // Resolve module or path to module\n let module = MODULE_BY_ROUTE[route];\n\n // Skip processing if the route path was not initialized.\n if (module === undefined && !NODE_ENV_IS_DEVELOPMENT) {\n continue;\n }\n\n // Module was not loaded yet?\n try {\n if (typeof module === \"string\") {\n // Production: Load and cache module only via pre-calculated path.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `route` values.\n module = MODULE_BY_ROUTE[route] = await import(pathToFileURL(join(CWD, module)).href);\n } else if (module === undefined && NODE_ENV_IS_DEVELOPMENT) {\n // Only map module paths depending on `route` during development.\n const modulePath = join(CWD, \"dist\", `${route}.js`);\n if (typeof require === \"function\" && require.cache[modulePath]) {\n // Bun: Remove module from cache before importing\n // as query parameter for import is ignored.\n delete require.cache[modulePath];\n }\n // Use timestamp as query parameter to update modules.\n const mtime = (await stat(modulePath)).mtime.getTime();\n // Dynamic imports are restricted to development environments;\n // therefore, production-level path validation is not required here.\n module = await import(`${pathToFileURL(modulePath).href}?${mtime}`);\n }\n } catch (e) {\n // Only stat() reports a missing route module (development).\n // ERR_MODULE_NOT_FOUND isn't ignored, as it is also thrown\n // for missing imports of an existing module.\n switch ((e as any)?.code) {\n case \"ENOENT\":\n case \"ENOTDIR\":\n continue;\n default:\n // Module exists, but fails to load.\n throw e;\n }\n }\n\n // Store current route in request.\n request.route = route;\n\n // Ensure module is a valid object before processing.\n if (module && typeof module === \"object\") {\n response =\n typeof module.default === \"function\"\n ? // Call functions with context as `this` and props as parameters,\n await module.default.call(context, props)\n : // otherwise return default export.\n module.default;\n }\n\n if (reply.sent) {\n return;\n } else if (route.endsWith(\"/[404]\")) {\n // Preserve existing status if a 404 page is requested directly\n // (e.g. \"/a/404\" answered by \"/a/[404]\", but not by \"/[404]\").\n // If no status is defined, set status to 404 automatically.\n if (\n reply.statusCode === 200 &&\n !(route.slice(0, -\"[404]\".length) + \"404\" === request.path.replace(/\\/$/, \"\"))\n ) {\n reply.status(404);\n }\n break;\n } else if (\n typeof response === \"string\" ||\n response instanceof Response ||\n response instanceof Readable ||\n response instanceof ReadableStream ||\n Buffer.isBuffer(response) ||\n isJsxNode(response)\n ) {\n break;\n } else if (typeof response === \"object\" && route.endsWith(\"/[...guard]\")) {\n // Add object entries from guard to props\n Object.assign(props, response);\n // Props are not a response: never send them if no other route answers.\n response = undefined;\n continue;\n } else if (response == null) {\n continue;\n } else {\n break;\n }\n }\n // No route answered and no [404] exists: don't reply with an empty 200.\n if (response == null && reply.statusCode === 200) {\n reply.status(404);\n }\n\n return await renderResponse(context, response);\n}\n\n// Names of special routes which must not be requested as \"/a/b/[c]\".\n// \"--metadata--\" is the route and file mapping of a production build, which must\n// not be served if the server runs in development mode on a production build.\nconst SPECIAL_SEGMENTS = new Set([\"index\", \"...guard\", \"...path\", \"--metadata--\"]);\n\n/**\n * Generates all possible routes based on the given input path.\n *\n * Example routes for \"/a/b/c\":\n *\n * [\n * \"/[...guard]\",\"/a/[...guard]\",\"/a/b/[...guard]\",\"/a/b/c/[...guard]\",\n * STATIC_FILE_ROUTE,\n * \"/a/b/[c]\",\"/a/b/c/[index]\",\n * \"/a/b/c/[...path]\",\"/a/b/[...path]\",\"/a/[...path]\",\"/[...path]\",\n * \"/a/b/c/[404]\",\"/a/b/[404]\",\"/a/[404]\",\"/[404]\"\n * ]\n */\nfunction* generateRoutes(path: string): Generator<string | typeof STATIC_FILE_ROUTE> {\n // Transform given path into array of all its segments.\n // \"/a/b/c\" => [\"\", \"/a\", \"/a/b\", \"/a/b/c\"]\n const segments = [\"\"];\n let edgeSegment = \"\";\n for (const segment of path.split(\"/\")) {\n // Ignore redundant slashes.\n if (segment !== \"\") {\n edgeSegment += `/${segment}`;\n segments.push(edgeSegment);\n }\n }\n\n // [...guard]s are pushed from root to edge.\n for (let i = 0; i < segments.length; i++) {\n yield `${segments[i]}/[...guard]`;\n }\n\n // Append the marker for static files\n yield STATIC_FILE_ROUTE;\n\n // \"/a/b/c\" => [\"/a/b/[c]\", \"/a/b/c/[index]\"]\n // Special routes aren't reachable by name (\"/index\", \"/...guard\", \"/...path\", \"/--metadata--\"),\n // except \"/a/404\", which is answered by \"/a/[404]\" on purpose.\n const lastSlash = edgeSegment.lastIndexOf(\"/\") + 1;\n const lastSegment = edgeSegment.substring(lastSlash);\n // Compared in lower case, as case-insensitive file systems (macOS, Windows) would\n // otherwise resolve e.g. \"/--METADATA--\" to \"[--metadata--].js\" in development.\n if (lastSlash > 0 && !SPECIAL_SEGMENTS.has(lastSegment.toLowerCase())) {\n yield `${edgeSegment.substring(0, lastSlash)}[${lastSegment}]`;\n }\n yield `${edgeSegment}/[index]`;\n\n // [...path]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[...path]`;\n }\n\n // [404]s are pushed from edge to root.\n for (let i = segments.length - 1; i >= 0; i--) {\n yield `${segments[i]}/[404]`;\n }\n}\n\n/**\n * Extracts the path from a request url (dropping the query string) and decodes it.\n * Backslashes become slashes (as in browsers) and repeated slashes are collapsed,\n * so a path like \"//evil.com\" can't be mistaken for another host, e.g. in a <base> tag.\n * Returns undefined for malformed encodings, null bytes and \".\" or \"..\" segments,\n * as browsers never send them: \"..\" could escape the project directory, and \".\"\n * would be resolved by the file system in development, but not in production.\n */\nfunction decodePath(url: string): string | undefined {\n try {\n const index = url.indexOf(\"?\");\n const rawPath = index === -1 ? url : url.slice(0, index);\n const path = decodeURIComponent(rawPath).replace(/[\\\\/]+/g, \"/\");\n if (path.includes(\"\\0\") || /(^|\\/)\\.\\.?(\\/|$)/.test(path)) {\n return undefined;\n }\n return path;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Renders JSX as stream and applies the optional response handler to the response.\n */\nasync function renderResponse(context: any, response: unknown) {\n const payload = isJsxNode(response) ? await jsxToStream.call(context, response) : response;\n // Post-process the payload with an optional response handler\n const responseHandler = context[\"responseHandler\"];\n return typeof responseHandler === \"function\"\n ? await responseHandler.call(context, payload)\n : payload;\n}\n\n/**\n * Returns stream and metadata for requested file.\n */\nasync function tryFile(request: FastifyRequest): Promise<BaseSendResult | undefined> {\n // Files can only be fetched; other methods (e.g. a POST to \"/index.css\") are left to the routes.\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n return undefined;\n }\n\n // Production: Retrieve files only from pre-initialized mapping.\n // This avoids potential path traversal vulnerabilities caused\n // by unexpected `request.path` values.\n const file = FILE_BY_PATH[request.path];\n if (file) {\n return await sendFile(request, file);\n }\n\n if (NODE_ENV_IS_DEVELOPMENT) {\n for (const directory of [\"dist\", \"public\"]) {\n // Don't expose compiled server routes and their other outputs (sourcemaps, CSS).\n // Only the last segment counts, like in the production build (see build.js).\n // The suffix after \"].\" is not anchored, as some file systems ignore trailing\n // dots and stream names (Windows: \"[index].js.\", \"[index].js::$DATA\").\n if (directory === \"dist\" && /\\[[^/]+\\]\\.[^/]*$/u.test(request.path)) {\n continue;\n }\n try {\n if ((await stat(join(CWD, directory, request.path))).isFile()) {\n // Dynamic path loading is restricted to development environments;\n // therefore, production-level path validation is not required here.\n return await sendFile(request, `${directory}${request.path}`);\n }\n } catch {\n continue;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Prepares a file for sending. Returns undefined if there is nothing to send,\n * so routes like [...path] or [404] can answer instead of the plain error page of fastifySend.\n */\nasync function sendFile(\n request: FastifyRequest,\n path: string,\n): Promise<BaseSendResult | undefined> {\n // fastifySend decodes the path, so it must be encoded.\n const result = await fastifySend(request.raw, encodeURI(path), FASTIFY_SEND_OPTIONS);\n // 404: file is missing or ignored, e.g. dotfiles by default.\n // Other errors (403 for denied dotfiles, 412, 416 for invalid ranges) belong to the file.\n if (result.type === \"error\" && result.statusCode === 404) {\n result.stream.destroy();\n return undefined;\n }\n return result;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["fastify"]
|
|
7
7
|
}
|