appstage 0.2.11 → 0.2.12
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.js +7 -8
- package/dist/index.cjs +28 -9
- package/dist/index.d.ts +3 -1
- package/dist/index.mjs +28 -9
- package/package.json +1 -1
- package/src/controllers/files.ts +53 -13
- package/src/scripts/utils/buildServer.ts +2 -2
- package/src/scripts/utils/{populateEntries.ts → setEntriesExport.ts} +5 -6
package/dist/bin.js
CHANGED
|
@@ -460,7 +460,7 @@ async function buildClient({ clientDir, watch, watchClient }, plugins) {
|
|
|
460
460
|
// src/scripts/utils/buildServer.ts
|
|
461
461
|
import esbuild2 from "esbuild";
|
|
462
462
|
|
|
463
|
-
// src/scripts/utils/
|
|
463
|
+
// src/scripts/utils/setEntriesExport.ts
|
|
464
464
|
import { writeFile } from "node:fs/promises";
|
|
465
465
|
|
|
466
466
|
// src/scripts/utils/toImportPath.ts
|
|
@@ -474,25 +474,24 @@ function toImportPath(relativePath, referencePath = ".") {
|
|
|
474
474
|
return importPath;
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
// src/scripts/utils/
|
|
478
|
-
async function
|
|
477
|
+
// src/scripts/utils/setEntriesExport.ts
|
|
478
|
+
async function setEntriesExport({ entriesPath }) {
|
|
479
479
|
if (entriesPath === null) return;
|
|
480
480
|
let serverEntries = await getEntryPoints(["server", "server/index"]);
|
|
481
481
|
let content = "";
|
|
482
482
|
if (serverEntries.length === 0) content = "export const entries = [];";
|
|
483
483
|
else {
|
|
484
484
|
content = "export const entries = (\n await Promise.all([";
|
|
485
|
-
for (let i = 0; i < serverEntries.length; i++)
|
|
485
|
+
for (let i = 0; i < serverEntries.length; i++)
|
|
486
486
|
content += `
|
|
487
|
-
// ${serverEntries[i].name}
|
|
488
487
|
import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
489
|
-
}
|
|
490
488
|
content += "\n ])\n).map(({ server }) => server);";
|
|
491
489
|
}
|
|
492
490
|
await writeFile(
|
|
493
491
|
entriesPath ?? "src/server/entries.ts",
|
|
494
492
|
`// Populated automatically during the build phase by picking
|
|
495
|
-
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)"
|
|
493
|
+
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)".
|
|
494
|
+
// Ignore this file if a custom set of entry exports is required.
|
|
496
495
|
${content}
|
|
497
496
|
`
|
|
498
497
|
);
|
|
@@ -502,7 +501,7 @@ ${content}
|
|
|
502
501
|
var appServerEntryPoints = ["src/server/index.ts"];
|
|
503
502
|
async function buildServer(params, plugins) {
|
|
504
503
|
let { serverDir, watch, watchServer } = params;
|
|
505
|
-
await
|
|
504
|
+
await setEntriesExport(params);
|
|
506
505
|
let buildOptions = {
|
|
507
506
|
...commonBuildOptions,
|
|
508
507
|
entryPoints: appServerEntryPoints,
|
package/dist/index.cjs
CHANGED
|
@@ -68,6 +68,15 @@ function getLanguageList(req) {
|
|
|
68
68
|
}
|
|
69
69
|
return Array.from(langs);
|
|
70
70
|
}
|
|
71
|
+
function matches(x, matcher) {
|
|
72
|
+
if (matcher === null || matcher === void 0) return true;
|
|
73
|
+
if (typeof matcher === "function") return matcher(x);
|
|
74
|
+
let patterns = Array.isArray(matcher) ? matcher : [matcher];
|
|
75
|
+
for (let pattern of patterns) if (pattern instanceof RegExp) {
|
|
76
|
+
if (pattern.test(x)) return true;
|
|
77
|
+
} else if (pattern === x) return true;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
71
80
|
const defaultExtensions = ["html", "htm"];
|
|
72
81
|
const defaultPath = (req) => req.originalUrl.split("?")[0];
|
|
73
82
|
const defaultLanguages = getLanguageList;
|
|
@@ -82,6 +91,14 @@ const files = (params) => {
|
|
|
82
91
|
return async (req, res) => {
|
|
83
92
|
let langs = (p.languages ?? defaultLanguages)(req);
|
|
84
93
|
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
94
|
+
if (!matches(path, p.matches)) {
|
|
95
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
96
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
97
|
+
code: "unmatched_path",
|
|
98
|
+
path
|
|
99
|
+
}));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
85
102
|
if (path.includes("../")) {
|
|
86
103
|
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
87
104
|
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
@@ -93,10 +110,12 @@ const files = (params) => {
|
|
|
93
110
|
let filePath = null;
|
|
94
111
|
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
95
112
|
let base = bases[k];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
if (!path.endsWith("/")) {
|
|
114
|
+
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
115
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
116
|
+
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
117
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
118
|
+
}
|
|
100
119
|
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}`, `index.${exts[j]}`);
|
|
101
120
|
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, path, `index.${langs[i]}.${exts[j]}`);
|
|
102
121
|
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
@@ -410,19 +429,19 @@ function toImportPath(relativePath, referencePath = ".") {
|
|
|
410
429
|
return importPath;
|
|
411
430
|
}
|
|
412
431
|
|
|
413
|
-
async function
|
|
432
|
+
async function setEntriesExport({ entriesPath }) {
|
|
414
433
|
if (entriesPath === null) return;
|
|
415
434
|
let serverEntries = await getEntryPoints(["server", "server/index"]);
|
|
416
435
|
let content = "";
|
|
417
436
|
if (serverEntries.length === 0) content = "export const entries = [];";
|
|
418
437
|
else {
|
|
419
438
|
content = "export const entries = (\n await Promise.all([";
|
|
420
|
-
for (let i = 0; i < serverEntries.length; i++) content += `\n
|
|
421
|
-
import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
439
|
+
for (let i = 0; i < serverEntries.length; i++) content += `\n import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
422
440
|
content += "\n ])\n).map(({ server }) => server);";
|
|
423
441
|
}
|
|
424
442
|
await (0, node_fs_promises.writeFile)(entriesPath ?? "src/server/entries.ts", `// Populated automatically during the build phase by picking
|
|
425
|
-
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)"
|
|
443
|
+
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)".
|
|
444
|
+
// Ignore this file if a custom set of entry exports is required.
|
|
426
445
|
${content}
|
|
427
446
|
`);
|
|
428
447
|
}
|
|
@@ -430,7 +449,7 @@ ${content}
|
|
|
430
449
|
const appServerEntryPoints = ["src/server/index.ts"];
|
|
431
450
|
async function buildServer(params, plugins) {
|
|
432
451
|
let { serverDir, watch, watchServer } = params;
|
|
433
|
-
await
|
|
452
|
+
await setEntriesExport(params);
|
|
434
453
|
let buildOptions = {
|
|
435
454
|
...commonBuildOptions,
|
|
436
455
|
entryPoints: appServerEntryPoints,
|
package/dist/index.d.ts
CHANGED
|
@@ -16,9 +16,11 @@ type TransformContent = (req: Request, res: Response, params: {
|
|
|
16
16
|
name?: string;
|
|
17
17
|
}) => string | Promise<string>;
|
|
18
18
|
|
|
19
|
+
type StringMatcher = string | RegExp | (string | RegExp)[] | ((x: string) => boolean) | null;
|
|
19
20
|
type FilesParams = {
|
|
20
21
|
base: string | string[];
|
|
21
|
-
path?: string | ((req: Request) => string);
|
|
22
|
+
path?: string | ((req: Request) => string); /** Specifies which paths should be accepted. */
|
|
23
|
+
matches?: StringMatcher;
|
|
22
24
|
extensions?: string[];
|
|
23
25
|
languages?: (req: Request) => string[];
|
|
24
26
|
transform?: TransformContent[];
|
package/dist/index.mjs
CHANGED
|
@@ -42,6 +42,15 @@ function getLanguageList(req) {
|
|
|
42
42
|
}
|
|
43
43
|
return Array.from(langs);
|
|
44
44
|
}
|
|
45
|
+
function matches(x, matcher) {
|
|
46
|
+
if (matcher === null || matcher === void 0) return true;
|
|
47
|
+
if (typeof matcher === "function") return matcher(x);
|
|
48
|
+
let patterns = Array.isArray(matcher) ? matcher : [matcher];
|
|
49
|
+
for (let pattern of patterns) if (pattern instanceof RegExp) {
|
|
50
|
+
if (pattern.test(x)) return true;
|
|
51
|
+
} else if (pattern === x) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
45
54
|
const defaultExtensions = ["html", "htm"];
|
|
46
55
|
const defaultPath = (req) => req.originalUrl.split("?")[0];
|
|
47
56
|
const defaultLanguages = getLanguageList;
|
|
@@ -56,6 +65,14 @@ const files = (params) => {
|
|
|
56
65
|
return async (req, res) => {
|
|
57
66
|
let langs = (p.languages ?? defaultLanguages)(req);
|
|
58
67
|
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
68
|
+
if (!matches(path, p.matches)) {
|
|
69
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
70
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
71
|
+
code: "unmatched_path",
|
|
72
|
+
path
|
|
73
|
+
}));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
59
76
|
if (path.includes("../")) {
|
|
60
77
|
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
61
78
|
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
@@ -67,10 +84,12 @@ const files = (params) => {
|
|
|
67
84
|
let filePath = null;
|
|
68
85
|
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
69
86
|
let base = bases[k];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
if (!path.endsWith("/")) {
|
|
88
|
+
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
89
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
90
|
+
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
91
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
92
|
+
}
|
|
74
93
|
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${path}.${langs[i]}`, `index.${exts[j]}`);
|
|
75
94
|
for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, path, `index.${langs[i]}.${exts[j]}`);
|
|
76
95
|
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
@@ -384,19 +403,19 @@ function toImportPath(relativePath, referencePath = ".") {
|
|
|
384
403
|
return importPath;
|
|
385
404
|
}
|
|
386
405
|
|
|
387
|
-
async function
|
|
406
|
+
async function setEntriesExport({ entriesPath }) {
|
|
388
407
|
if (entriesPath === null) return;
|
|
389
408
|
let serverEntries = await getEntryPoints(["server", "server/index"]);
|
|
390
409
|
let content = "";
|
|
391
410
|
if (serverEntries.length === 0) content = "export const entries = [];";
|
|
392
411
|
else {
|
|
393
412
|
content = "export const entries = (\n await Promise.all([";
|
|
394
|
-
for (let i = 0; i < serverEntries.length; i++) content += `\n
|
|
395
|
-
import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
413
|
+
for (let i = 0; i < serverEntries.length; i++) content += `\n import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
396
414
|
content += "\n ])\n).map(({ server }) => server);";
|
|
397
415
|
}
|
|
398
416
|
await writeFile(entriesPath ?? "src/server/entries.ts", `// Populated automatically during the build phase by picking
|
|
399
|
-
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)"
|
|
417
|
+
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)".
|
|
418
|
+
// Ignore this file if a custom set of entry exports is required.
|
|
400
419
|
${content}
|
|
401
420
|
`);
|
|
402
421
|
}
|
|
@@ -404,7 +423,7 @@ ${content}
|
|
|
404
423
|
const appServerEntryPoints = ["src/server/index.ts"];
|
|
405
424
|
async function buildServer(params, plugins) {
|
|
406
425
|
let { serverDir, watch, watchServer } = params;
|
|
407
|
-
await
|
|
426
|
+
await setEntriesExport(params);
|
|
408
427
|
let buildOptions = {
|
|
409
428
|
...commonBuildOptions,
|
|
410
429
|
entryPoints: appServerEntryPoints,
|
package/package.json
CHANGED
package/src/controllers/files.ts
CHANGED
|
@@ -5,6 +5,13 @@ import type { Controller } from "../types/Controller.ts";
|
|
|
5
5
|
import type { TransformContent } from "../types/TransformContent.ts";
|
|
6
6
|
import { emitLog } from "../utils/emitLog.ts";
|
|
7
7
|
|
|
8
|
+
type StringMatcher =
|
|
9
|
+
| string
|
|
10
|
+
| RegExp
|
|
11
|
+
| (string | RegExp)[]
|
|
12
|
+
| ((x: string) => boolean)
|
|
13
|
+
| null;
|
|
14
|
+
|
|
8
15
|
const maxLanguages = 3;
|
|
9
16
|
|
|
10
17
|
async function resolve(...parts: string[]) {
|
|
@@ -38,9 +45,27 @@ function getLanguageList(req: Request) {
|
|
|
38
45
|
return Array.from(langs);
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
function matches(x: string, matcher: StringMatcher | undefined) {
|
|
49
|
+
if (matcher === null || matcher === undefined) return true;
|
|
50
|
+
|
|
51
|
+
if (typeof matcher === "function") return matcher(x);
|
|
52
|
+
|
|
53
|
+
let patterns = Array.isArray(matcher) ? matcher : [matcher];
|
|
54
|
+
|
|
55
|
+
for (let pattern of patterns) {
|
|
56
|
+
if (pattern instanceof RegExp) {
|
|
57
|
+
if (pattern.test(x)) return true;
|
|
58
|
+
} else if (pattern === x) return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
export type FilesParams = {
|
|
42
65
|
base: string | string[];
|
|
43
66
|
path?: string | ((req: Request) => string);
|
|
67
|
+
/** Specifies which paths should be accepted. */
|
|
68
|
+
matches?: StringMatcher;
|
|
44
69
|
extensions?: string[];
|
|
45
70
|
languages?: (req: Request) => string[];
|
|
46
71
|
transform?: TransformContent[];
|
|
@@ -66,6 +91,19 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
66
91
|
let path =
|
|
67
92
|
typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
68
93
|
|
|
94
|
+
if (!matches(path, p.matches)) {
|
|
95
|
+
emitLog(req.app, "Unmatched path", { data: { path } });
|
|
96
|
+
|
|
97
|
+
res.status(404).send(
|
|
98
|
+
await req.app.renderStatus?.(req, res, {
|
|
99
|
+
code: "unmatched_path",
|
|
100
|
+
path,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
69
107
|
if (path.includes("../")) {
|
|
70
108
|
emitLog(req.app, "Invalid path (potential traversal attempt)", {
|
|
71
109
|
data: { path },
|
|
@@ -88,22 +126,24 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
88
126
|
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
89
127
|
let base = bases[k];
|
|
90
128
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
if (!path.endsWith("/")) {
|
|
130
|
+
// /x.en /x.ru
|
|
131
|
+
for (let i = 0; i < langs.length && filePath === null; i++)
|
|
132
|
+
filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
94
133
|
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
// /x
|
|
135
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
97
136
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
// /x.en.html /x.en.htm /x.ru.html /x.ru.htm
|
|
138
|
+
for (let i = 0; i < langs.length && filePath === null; i++) {
|
|
139
|
+
for (let j = 0; j < exts.length && filePath === null; j++)
|
|
140
|
+
filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
141
|
+
}
|
|
103
142
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
143
|
+
// /x.html /x.htm
|
|
144
|
+
for (let i = 0; i < exts.length && filePath === null; i++)
|
|
145
|
+
filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
146
|
+
}
|
|
107
147
|
|
|
108
148
|
// /x.en/index.html /x.en/index.htm /x.ru/index.html /x.ru/index.htm
|
|
109
149
|
for (let i = 0; i < langs.length && filePath === null; i++) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import esbuild, { type BuildOptions, type Plugin } from "esbuild";
|
|
2
2
|
import { commonBuildOptions } from "../const/commonBuildOptions.ts";
|
|
3
3
|
import type { BuildParams } from "../types/BuildParams.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { setEntriesExport } from "./setEntriesExport.ts";
|
|
5
5
|
|
|
6
6
|
const appServerEntryPoints = ["src/server/index.ts"];
|
|
7
7
|
|
|
8
8
|
export async function buildServer(params: BuildParams, plugins?: Plugin[]) {
|
|
9
9
|
let { serverDir, watch, watchServer } = params;
|
|
10
10
|
|
|
11
|
-
await
|
|
11
|
+
await setEntriesExport(params);
|
|
12
12
|
|
|
13
13
|
let buildOptions: BuildOptions = {
|
|
14
14
|
...commonBuildOptions,
|
|
@@ -3,7 +3,7 @@ import type { BuildParams } from "../types/BuildParams.ts";
|
|
|
3
3
|
import { getEntryPoints } from "./getEntryPoints.ts";
|
|
4
4
|
import { toImportPath } from "./toImportPath.ts";
|
|
5
5
|
|
|
6
|
-
export async function
|
|
6
|
+
export async function setEntriesExport({ entriesPath }: BuildParams) {
|
|
7
7
|
if (entriesPath === null) return;
|
|
8
8
|
|
|
9
9
|
let serverEntries = await getEntryPoints(["server", "server/index"]);
|
|
@@ -13,10 +13,8 @@ export async function populateEntries({ entriesPath }: BuildParams) {
|
|
|
13
13
|
else {
|
|
14
14
|
content = "export const entries = (\n await Promise.all([";
|
|
15
15
|
|
|
16
|
-
for (let i = 0; i < serverEntries.length; i++)
|
|
17
|
-
content += `\n
|
|
18
|
-
import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
19
|
-
}
|
|
16
|
+
for (let i = 0; i < serverEntries.length; i++)
|
|
17
|
+
content += `\n import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
|
|
20
18
|
|
|
21
19
|
content += "\n ])\n).map(({ server }) => server);";
|
|
22
20
|
}
|
|
@@ -24,7 +22,8 @@ export async function populateEntries({ entriesPath }: BuildParams) {
|
|
|
24
22
|
await writeFile(
|
|
25
23
|
entriesPath ?? "src/server/entries.ts",
|
|
26
24
|
`// Populated automatically during the build phase by picking
|
|
27
|
-
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)"
|
|
25
|
+
// all server exports from "src/entries/<entry_name>/server(/index)?.(js|ts)".
|
|
26
|
+
// Ignore this file if a custom set of entry exports is required.
|
|
28
27
|
${content}
|
|
29
28
|
`,
|
|
30
29
|
);
|