wurzel 0.0.15 → 0.0.16
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/dist/bin/wurzel.js +2 -2
- package/dist/lib/index.js +80 -31
- package/dist/samples/express/main.js +2 -2
- package/package.json +9 -5
package/dist/bin/wurzel.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import express from "express";
|
|
4
|
-
import { expressRouter
|
|
4
|
+
import { expressRouter } from "../lib/index.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
const app = express();
|
|
8
8
|
|
|
9
9
|
const rootFolder = path.resolve(".");
|
|
10
10
|
|
|
11
|
-
app.use("/",
|
|
11
|
+
app.use("/", expressRouter({ express, baseFolder: rootFolder }));
|
|
12
12
|
|
|
13
13
|
const port = 8080;
|
|
14
14
|
|
package/dist/lib/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
|
-
import
|
|
14
|
+
import * as importMetaResolve from "import-meta-resolve";
|
|
15
15
|
// @ts-expect-error missing types
|
|
16
16
|
import { transpileCode } from "commentscript";
|
|
17
17
|
import { LRUCache } from "lru-cache";
|
|
@@ -41,7 +41,7 @@ const defaultResolveImportPath/*: TResolveImportPathFunc*/ = async ({ importer,
|
|
|
41
41
|
let resolvedUrl/*: string | undefined*/ = undefined;
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
resolvedUrl =
|
|
44
|
+
resolvedUrl = importMetaResolve.resolve(specifier, parentUrl.toString());
|
|
45
45
|
} catch (error) {
|
|
46
46
|
return {
|
|
47
47
|
error: error /*as Error*/
|
|
@@ -67,6 +67,40 @@ const defaultResolveImportPath/*: TResolveImportPathFunc*/ = async ({ importer,
|
|
|
67
67
|
};
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
// es6-debug-server rejects uris containing "//" or "..", and a file path must not contain null bytes,
|
|
71
|
+
// so such uris have to be answered before reaching es6-debug-server
|
|
72
|
+
const isMalformedUri = ({ uri }/*: { uri: string }*/) => {
|
|
73
|
+
return uri.includes("//") || uri.split("/").includes("..") || uri.includes("\0");
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const decodePath = ({ path }/*: { path: string }*/) => {
|
|
77
|
+
try {
|
|
78
|
+
return decodeURIComponent(path);
|
|
79
|
+
} catch (ex) {
|
|
80
|
+
const error/*: Error & { status?: number }*/ = Error(`failed to decode path "${path}"`, { cause: ex });
|
|
81
|
+
// express answers errors with their status
|
|
82
|
+
// eslint-disable-next-line immutable/no-mutation
|
|
83
|
+
error.status = 400;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const encodePath = ({ path }/*: { path: string }*/) => {
|
|
89
|
+
return path.split("/").map((segment) => {
|
|
90
|
+
return encodeURIComponent(segment);
|
|
91
|
+
}).join("/");
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const queryOf = ({ url }/*: { url: string }*/) => {
|
|
95
|
+
const queryStart = url.indexOf("?");
|
|
96
|
+
return queryStart < 0 ? "" : url.substring(queryStart);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ENOTDIR means that a parent folder of the path is a file, so there is no such file either
|
|
100
|
+
const isFileNotFoundError = ({ error }/*: { error: NodeJS.ErrnoException }*/) => {
|
|
101
|
+
return error.code === "ENOENT" || error.code === "ENOTDIR";
|
|
102
|
+
};
|
|
103
|
+
|
|
70
104
|
const defaultScriptFileEndings = [".js", ".ts", ".cjs", ".mjs", ".cts", ".mts"];
|
|
71
105
|
|
|
72
106
|
const defaultDetermineFileTypeByPath = ({ filePath }/*: { filePath: string }*/)/*: TFileType*/ => {
|
|
@@ -99,7 +133,6 @@ const expressRouter = ({
|
|
|
99
133
|
maxTranspileCacheSize?: number,
|
|
100
134
|
|
|
101
135
|
analyzeCode?: TCodeAnalyzeFunc,
|
|
102
|
-
// eslint-disable-next-line no-unused-vars
|
|
103
136
|
determineFileTypeByPath?: (args: { filePath: string }) => TFileType,
|
|
104
137
|
resolveImportPath?: TResolveImportPathFunc
|
|
105
138
|
// eslint-disable-next-line complexity
|
|
@@ -107,15 +140,19 @@ const expressRouter = ({
|
|
|
107
140
|
|
|
108
141
|
const router = express.Router();
|
|
109
142
|
|
|
143
|
+
// eslint-disable-next-line k13-engineering/no-new
|
|
110
144
|
const transpileCache = new LRUCache/*<string, string>*/({
|
|
111
145
|
maxSize: maxTranspileCacheSize,
|
|
146
|
+
// eslint-disable-next-line k13-engineering/prefer-single-object-parameters
|
|
112
147
|
sizeCalculation: (value, key) => {
|
|
113
148
|
return key.length + value.length;
|
|
114
149
|
}
|
|
115
150
|
});
|
|
116
151
|
|
|
152
|
+
// eslint-disable-next-line k13-engineering/no-new
|
|
117
153
|
const analyzeCache = new LRUCache/*<string, ICodeAnalyzeResult>*/({
|
|
118
154
|
maxSize: maxAnalyzeCacheSize,
|
|
155
|
+
// eslint-disable-next-line k13-engineering/prefer-single-object-parameters
|
|
119
156
|
sizeCalculation: (value, key) => {
|
|
120
157
|
return key.length + JSON.stringify(value).length;
|
|
121
158
|
}
|
|
@@ -177,7 +214,7 @@ const expressRouter = ({
|
|
|
177
214
|
});
|
|
178
215
|
|
|
179
216
|
if (readError !== undefined) {
|
|
180
|
-
if (readError
|
|
217
|
+
if (isFileNotFoundError({ error: readError })) {
|
|
181
218
|
|
|
182
219
|
return {
|
|
183
220
|
error: createReadError({
|
|
@@ -244,45 +281,57 @@ const expressRouter = ({
|
|
|
244
281
|
resolveImportPath
|
|
245
282
|
});
|
|
246
283
|
|
|
284
|
+
const serveScript = ({ filePath, req, res }/*: { filePath: string, req: Express.Request, res: Express.Response }*/) => {
|
|
285
|
+
|
|
286
|
+
if (isMalformedUri({ uri: filePath })) {
|
|
287
|
+
res.status(400).end("bad request");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
server.handleRequest({
|
|
292
|
+
uri: filePath,
|
|
293
|
+
|
|
294
|
+
// a relative redirect is resolved by the client against the url it requested, which keeps it below
|
|
295
|
+
// wherever the router is reachable, even below a path prefix of a reverse proxy the router cannot see
|
|
296
|
+
handleRedirect: ({ relativeUri }) => {
|
|
297
|
+
const redirectLocation = `${encodePath({ path: relativeUri })}${queryOf({ url: req.url })}`;
|
|
298
|
+
res.redirect(redirectLocation);
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
handleContent: ({ contentType, content }) => {
|
|
302
|
+
res.writeHead(200, {
|
|
303
|
+
"Content-Type": contentType
|
|
304
|
+
});
|
|
305
|
+
res.end(content);
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
handleFileNotFound: () => {
|
|
309
|
+
res.status(404).end("not found");
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
handleInternalError: ({ error }) => {
|
|
313
|
+
console.error(error);
|
|
314
|
+
res.status(500).end("internal server error");
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// eslint-disable-next-line k13-engineering/prefer-single-object-parameters
|
|
247
320
|
router.use((req, res, next) => {
|
|
248
321
|
|
|
249
322
|
if (req.method === "HEAD") {
|
|
250
323
|
throw Error("HEAD not supported yet");
|
|
251
324
|
}
|
|
252
325
|
|
|
253
|
-
const
|
|
326
|
+
const filePath = decodePath({ path: req.path });
|
|
327
|
+
const fileType = determineFileTypeByPath({ filePath });
|
|
254
328
|
|
|
255
329
|
if (fileType === "script-resource") {
|
|
256
330
|
throw Error(`file type ${fileType} is not supported yet`);
|
|
257
331
|
}
|
|
258
332
|
|
|
259
333
|
if (fileType === "script") {
|
|
260
|
-
|
|
261
|
-
server.handleRequest({
|
|
262
|
-
uri: req.url,
|
|
263
|
-
|
|
264
|
-
handleRedirect: ({ uri }) => {
|
|
265
|
-
const redirectLocation = `${req.baseUrl}${uri}`;
|
|
266
|
-
res.redirect(redirectLocation);
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
handleContent: ({ contentType, content }) => {
|
|
270
|
-
res.writeHead(200, {
|
|
271
|
-
"Content-Type": contentType
|
|
272
|
-
});
|
|
273
|
-
res.end(content);
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
handleFileNotFound: () => {
|
|
277
|
-
res.status(404).end("not found");
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
handleInternalError: ({ error }) => {
|
|
281
|
-
console.error(error);
|
|
282
|
-
res.status(500).end("internal server error");
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
334
|
+
serveScript({ filePath, req, res });
|
|
286
335
|
return;
|
|
287
336
|
}
|
|
288
337
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
expressRouter
|
|
2
|
+
expressRouter,
|
|
3
3
|
defaultResolveImportPath,
|
|
4
4
|
/*type TResolveImportPathFunc
|
|
5
5
|
*/} from "../../lib/index.js";
|
|
@@ -27,7 +27,7 @@ const resolveImportPath/*: TResolveImportPathFunc*/ = async ({ importer, specifi
|
|
|
27
27
|
|
|
28
28
|
const app = express();
|
|
29
29
|
|
|
30
|
-
app.use("/",
|
|
30
|
+
app.use("/", expressRouter({
|
|
31
31
|
express,
|
|
32
32
|
baseFolder,
|
|
33
33
|
resolveImportPath
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.16",
|
|
3
3
|
"name": "wurzel",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A tool to serve script files from different directories",
|
|
@@ -9,20 +9,24 @@
|
|
|
9
9
|
"main": "dist/lib/index.js",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"commentscript": "^0.0.10",
|
|
12
|
-
"es6-debug-server": "^0.0.
|
|
12
|
+
"es6-debug-server": "^0.0.14",
|
|
13
13
|
"esm-resource": "^0.0.3",
|
|
14
14
|
"import-meta-resolve": "^4.0.0",
|
|
15
15
|
"lru-cache": "^10.2.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@eslint/js": "^10.0.1",
|
|
19
|
+
"@k13engineering/eslint-rules": "^0.0.6",
|
|
19
20
|
"@k13engineering/releasetool": "^0.0.5",
|
|
20
21
|
"@types/express": "^4.17.21",
|
|
21
|
-
"@types/
|
|
22
|
+
"@types/mocha": "^10.0.10",
|
|
23
|
+
"@types/node": "^25.6.0",
|
|
24
|
+
"c8": "^11.0.0",
|
|
22
25
|
"deno-node": "^0.0.12",
|
|
23
26
|
"express": "^5.2.1",
|
|
27
|
+
"mocha": "^12.0.2",
|
|
24
28
|
"npm-check-updates": "^20.0.2",
|
|
25
|
-
"typescript
|
|
29
|
+
"typescript": "^6.0.3"
|
|
26
30
|
},
|
|
27
31
|
"bin": {
|
|
28
32
|
"wurzel": "dist/bin/wurzel.js"
|
|
@@ -30,7 +34,7 @@
|
|
|
30
34
|
"scripts": {
|
|
31
35
|
"build": "rm -rf dist/ && deno-node-build --root . --out dist/ --entry lib/index.ts --entry bin/wurzel.ts --entry samples/express/main.ts && cp -r samples/express/frontend dist/samples/express/",
|
|
32
36
|
"lint": "eslint .",
|
|
33
|
-
"test": "
|
|
37
|
+
"test": "c8 --reporter lcov --reporter html --reporter text --all --src lib/ --exclude 'lib/**/*.spec.ts' mocha 'lib/**/*.spec.ts'",
|
|
34
38
|
"type-check": "tsc --noEmit",
|
|
35
39
|
"update-deps": "npm-check-updates -u"
|
|
36
40
|
},
|