appstage 0.2.7 → 0.2.9
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 +6 -7
- package/dist/index.cjs +91 -7
- package/dist/index.d.ts +13 -1
- package/dist/index.mjs +91 -8
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/controllers/files.ts +151 -0
- package/src/scripts/utils/buildClient.ts +3 -5
- package/src/scripts/utils/buildServer.ts +3 -1
- package/src/scripts/utils/buildServerCSS.ts +3 -1
package/dist/bin.js
CHANGED
|
@@ -433,12 +433,9 @@ async function getEntryPoints(path) {
|
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
// src/scripts/utils/buildClient.ts
|
|
436
|
+
var entryClientPaths = ["ui/index", "client/index", "index", "src/index"];
|
|
436
437
|
async function buildClient({ clientDir, watch, watchClient }, plugins) {
|
|
437
|
-
let clientEntries = await getEntryPoints(
|
|
438
|
-
"ui/index",
|
|
439
|
-
"client/index",
|
|
440
|
-
"index"
|
|
441
|
-
]);
|
|
438
|
+
let clientEntries = await getEntryPoints(entryClientPaths);
|
|
442
439
|
let buildOptions = {
|
|
443
440
|
...commonBuildOptions,
|
|
444
441
|
entryPoints: clientEntries.map(({ path }) => path),
|
|
@@ -502,12 +499,13 @@ ${content}
|
|
|
502
499
|
}
|
|
503
500
|
|
|
504
501
|
// src/scripts/utils/buildServer.ts
|
|
502
|
+
var appServerEntryPoints = ["src/server/index.ts"];
|
|
505
503
|
async function buildServer(params, plugins) {
|
|
506
504
|
let { serverDir, watch, watchServer } = params;
|
|
507
505
|
await populateEntries(params);
|
|
508
506
|
let buildOptions = {
|
|
509
507
|
...commonBuildOptions,
|
|
510
|
-
entryPoints:
|
|
508
|
+
entryPoints: appServerEntryPoints,
|
|
511
509
|
bundle: true,
|
|
512
510
|
splitting: true,
|
|
513
511
|
outdir: `${serverDir}/server`,
|
|
@@ -528,8 +526,9 @@ async function buildServer(params, plugins) {
|
|
|
528
526
|
|
|
529
527
|
// src/scripts/utils/buildServerCSS.ts
|
|
530
528
|
import esbuild3 from "esbuild";
|
|
529
|
+
var entryServerPaths = ["server", "server/index"];
|
|
531
530
|
async function buildServerCSS({ serverDir, watch, watchServer }, plugins) {
|
|
532
|
-
let serverEntries = await getEntryPoints(
|
|
531
|
+
let serverEntries = await getEntryPoints(entryServerPaths);
|
|
533
532
|
let buildOptions = {
|
|
534
533
|
...commonBuildOptions,
|
|
535
534
|
entryPoints: serverEntries.map(({ name, path }) => ({
|
package/dist/index.cjs
CHANGED
|
@@ -124,6 +124,85 @@ const dir = ({ path, name = defaultName, ext = defaultExt, transform, supportedL
|
|
|
124
124
|
};
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
const maxLanguages = 3;
|
|
128
|
+
async function resolve(...parts) {
|
|
129
|
+
let fullPath = (0, node_path.join)(...parts);
|
|
130
|
+
try {
|
|
131
|
+
if ((await (0, node_fs_promises.lstat)(fullPath)).isFile()) return fullPath;
|
|
132
|
+
} catch {}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function getLanguageList(acceptedLanguages) {
|
|
136
|
+
let langs = /* @__PURE__ */ new Set();
|
|
137
|
+
for (let i = 0; i < acceptedLanguages.length && i < maxLanguages; i++) {
|
|
138
|
+
let s = acceptedLanguages[i];
|
|
139
|
+
let [lang] = s.split(/[-_]/);
|
|
140
|
+
if (s === lang) langs.add(s);
|
|
141
|
+
else {
|
|
142
|
+
langs.add(s);
|
|
143
|
+
langs.add(lang);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return Array.from(langs);
|
|
147
|
+
}
|
|
148
|
+
const defaultExtensions = ["html", "htm"];
|
|
149
|
+
const defaultPath = (req) => req.originalUrl.split("?")[0];
|
|
150
|
+
/**
|
|
151
|
+
* Serves files from the specified directory path in a locale-aware
|
|
152
|
+
* fashion after applying optional transforms.
|
|
153
|
+
*/
|
|
154
|
+
const files = (params) => {
|
|
155
|
+
let p = typeof params === "string" ? { base: params } : params;
|
|
156
|
+
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
157
|
+
let exts = p.extensions ?? defaultExtensions;
|
|
158
|
+
return async (req, res) => {
|
|
159
|
+
let langs = getLanguageList(req.acceptsLanguages());
|
|
160
|
+
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
161
|
+
if (path.includes("../")) {
|
|
162
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
163
|
+
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
164
|
+
code: "invalid_path",
|
|
165
|
+
path
|
|
166
|
+
}));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let filePath = null;
|
|
170
|
+
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
171
|
+
let base = bases[k];
|
|
172
|
+
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
173
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
174
|
+
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]}`);
|
|
175
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
176
|
+
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]}`);
|
|
177
|
+
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]}`);
|
|
178
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
179
|
+
}
|
|
180
|
+
if (filePath === null) {
|
|
181
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
182
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
183
|
+
code: "unknown_path",
|
|
184
|
+
path
|
|
185
|
+
}));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!p.transform?.length) {
|
|
189
|
+
res.sendFile(filePath);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let content = (await (0, node_fs_promises.readFile)(filePath)).toString();
|
|
193
|
+
let name = (0, node_path.basename)(filePath);
|
|
194
|
+
for (let transform of p.transform) {
|
|
195
|
+
let result = transform(req, res, {
|
|
196
|
+
content,
|
|
197
|
+
path,
|
|
198
|
+
name
|
|
199
|
+
});
|
|
200
|
+
content = result instanceof Promise ? await result : result;
|
|
201
|
+
}
|
|
202
|
+
res.type((0, node_path.extname)(name).slice(1)).send(content);
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
127
206
|
const unhandledError = () => async (err, req, res) => {
|
|
128
207
|
emitLog(req.app, "Unhandled error", {
|
|
129
208
|
level: "error",
|
|
@@ -363,15 +442,17 @@ async function getEntryPoints(path) {
|
|
|
363
442
|
}))).filter((item) => item !== void 0);
|
|
364
443
|
}
|
|
365
444
|
|
|
445
|
+
const entryClientPaths = [
|
|
446
|
+
"ui/index",
|
|
447
|
+
"client/index",
|
|
448
|
+
"index",
|
|
449
|
+
"src/index"
|
|
450
|
+
];
|
|
366
451
|
/**
|
|
367
452
|
* Builds the client-side code.
|
|
368
453
|
*/
|
|
369
454
|
async function buildClient({ clientDir, watch, watchClient }, plugins) {
|
|
370
|
-
let clientEntries = await getEntryPoints(
|
|
371
|
-
"ui/index",
|
|
372
|
-
"client/index",
|
|
373
|
-
"index"
|
|
374
|
-
]);
|
|
455
|
+
let clientEntries = await getEntryPoints(entryClientPaths);
|
|
375
456
|
let buildOptions = {
|
|
376
457
|
...commonBuildOptions,
|
|
377
458
|
entryPoints: clientEntries.map(({ path }) => path),
|
|
@@ -417,12 +498,13 @@ ${content}
|
|
|
417
498
|
`);
|
|
418
499
|
}
|
|
419
500
|
|
|
501
|
+
const appServerEntryPoints = ["src/server/index.ts"];
|
|
420
502
|
async function buildServer(params, plugins) {
|
|
421
503
|
let { serverDir, watch, watchServer } = params;
|
|
422
504
|
await populateEntries(params);
|
|
423
505
|
let buildOptions = {
|
|
424
506
|
...commonBuildOptions,
|
|
425
|
-
entryPoints:
|
|
507
|
+
entryPoints: appServerEntryPoints,
|
|
426
508
|
bundle: true,
|
|
427
509
|
splitting: true,
|
|
428
510
|
outdir: `${serverDir}/server`,
|
|
@@ -441,8 +523,9 @@ async function buildServer(params, plugins) {
|
|
|
441
523
|
await esbuild.default.build(buildOptions);
|
|
442
524
|
}
|
|
443
525
|
|
|
526
|
+
const entryServerPaths = ["server", "server/index"];
|
|
444
527
|
async function buildServerCSS({ serverDir, watch, watchServer }, plugins) {
|
|
445
|
-
let serverEntries = await getEntryPoints(
|
|
528
|
+
let serverEntries = await getEntryPoints(entryServerPaths);
|
|
446
529
|
let buildOptions = {
|
|
447
530
|
...commonBuildOptions,
|
|
448
531
|
entryPoints: serverEntries.map(({ name, path }) => ({
|
|
@@ -635,6 +718,7 @@ exports.createApp = createApp;
|
|
|
635
718
|
exports.cspNonce = cspNonce;
|
|
636
719
|
exports.dir = dir;
|
|
637
720
|
exports.emitLog = emitLog;
|
|
721
|
+
exports.files = files;
|
|
638
722
|
exports.getEffectiveLocale = getEffectiveLocale;
|
|
639
723
|
exports.getLocales = getLocales;
|
|
640
724
|
exports.getStatusMessage = getStatusMessage;
|
package/dist/index.d.ts
CHANGED
|
@@ -75,6 +75,18 @@ type DirParams = Partial<Pick<ResolveFilePathParams, "supportedLocales" | "index
|
|
|
75
75
|
*/
|
|
76
76
|
declare const dir$1: Controller<DirParams>;
|
|
77
77
|
|
|
78
|
+
type FilesParams = {
|
|
79
|
+
base: string | string[];
|
|
80
|
+
path?: string | ((req: Request) => string);
|
|
81
|
+
extensions?: string[];
|
|
82
|
+
transform?: TransformContent[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Serves files from the specified directory path in a locale-aware
|
|
86
|
+
* fashion after applying optional transforms.
|
|
87
|
+
*/
|
|
88
|
+
declare const files: Controller<string | FilesParams>;
|
|
89
|
+
|
|
78
90
|
type ErrorController<T = void> = (params: T) => ErrorRequestHandler;
|
|
79
91
|
|
|
80
92
|
declare const unhandledError: ErrorController;
|
|
@@ -1166,4 +1178,4 @@ declare function servePipeableStream(req: Request, res: Response): ({
|
|
|
1166
1178
|
pipe
|
|
1167
1179
|
}: PipeableStream, error?: unknown) => Promise<void>;
|
|
1168
1180
|
|
|
1169
|
-
export { Controller, DirParams, ErrorController, LangParams, LogEventPayload, LogLevel, LogOptions, Middleware, MiddlewareSet, RenderStatus, ReqCtx, ResolveFilePathParams, TransformContent, build, cli, createApp, cspNonce, dir$1 as dir, emitLog, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang$1 as lang, log, renderStatus, requestEvents, resolveFilePath, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
|
1181
|
+
export { Controller, DirParams, ErrorController, FilesParams, LangParams, LogEventPayload, LogLevel, LogOptions, Middleware, MiddlewareSet, RenderStatus, ReqCtx, ResolveFilePathParams, TransformContent, build, cli, createApp, cspNonce, dir$1 as dir, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang$1 as lang, log, renderStatus, requestEvents, resolveFilePath, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
package/dist/index.mjs
CHANGED
|
@@ -98,6 +98,85 @@ const dir = ({ path, name = defaultName, ext = defaultExt, transform, supportedL
|
|
|
98
98
|
};
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
const maxLanguages = 3;
|
|
102
|
+
async function resolve(...parts) {
|
|
103
|
+
let fullPath = join(...parts);
|
|
104
|
+
try {
|
|
105
|
+
if ((await lstat(fullPath)).isFile()) return fullPath;
|
|
106
|
+
} catch {}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function getLanguageList(acceptedLanguages) {
|
|
110
|
+
let langs = /* @__PURE__ */ new Set();
|
|
111
|
+
for (let i = 0; i < acceptedLanguages.length && i < maxLanguages; i++) {
|
|
112
|
+
let s = acceptedLanguages[i];
|
|
113
|
+
let [lang] = s.split(/[-_]/);
|
|
114
|
+
if (s === lang) langs.add(s);
|
|
115
|
+
else {
|
|
116
|
+
langs.add(s);
|
|
117
|
+
langs.add(lang);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return Array.from(langs);
|
|
121
|
+
}
|
|
122
|
+
const defaultExtensions = ["html", "htm"];
|
|
123
|
+
const defaultPath = (req) => req.originalUrl.split("?")[0];
|
|
124
|
+
/**
|
|
125
|
+
* Serves files from the specified directory path in a locale-aware
|
|
126
|
+
* fashion after applying optional transforms.
|
|
127
|
+
*/
|
|
128
|
+
const files = (params) => {
|
|
129
|
+
let p = typeof params === "string" ? { base: params } : params;
|
|
130
|
+
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
131
|
+
let exts = p.extensions ?? defaultExtensions;
|
|
132
|
+
return async (req, res) => {
|
|
133
|
+
let langs = getLanguageList(req.acceptsLanguages());
|
|
134
|
+
let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
135
|
+
if (path.includes("../")) {
|
|
136
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
|
|
137
|
+
res.status(400).send(await req.app.renderStatus?.(req, res, {
|
|
138
|
+
code: "invalid_path",
|
|
139
|
+
path
|
|
140
|
+
}));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let filePath = null;
|
|
144
|
+
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
145
|
+
let base = bases[k];
|
|
146
|
+
for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
147
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
148
|
+
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]}`);
|
|
149
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
150
|
+
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]}`);
|
|
151
|
+
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]}`);
|
|
152
|
+
for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
153
|
+
}
|
|
154
|
+
if (filePath === null) {
|
|
155
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
156
|
+
res.status(404).send(await req.app.renderStatus?.(req, res, {
|
|
157
|
+
code: "unknown_path",
|
|
158
|
+
path
|
|
159
|
+
}));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!p.transform?.length) {
|
|
163
|
+
res.sendFile(filePath);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let content = (await readFile(filePath)).toString();
|
|
167
|
+
let name = basename(filePath);
|
|
168
|
+
for (let transform of p.transform) {
|
|
169
|
+
let result = transform(req, res, {
|
|
170
|
+
content,
|
|
171
|
+
path,
|
|
172
|
+
name
|
|
173
|
+
});
|
|
174
|
+
content = result instanceof Promise ? await result : result;
|
|
175
|
+
}
|
|
176
|
+
res.type(extname(name).slice(1)).send(content);
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
101
180
|
const unhandledError = () => async (err, req, res) => {
|
|
102
181
|
emitLog(req.app, "Unhandled error", {
|
|
103
182
|
level: "error",
|
|
@@ -337,15 +416,17 @@ async function getEntryPoints(path) {
|
|
|
337
416
|
}))).filter((item) => item !== void 0);
|
|
338
417
|
}
|
|
339
418
|
|
|
419
|
+
const entryClientPaths = [
|
|
420
|
+
"ui/index",
|
|
421
|
+
"client/index",
|
|
422
|
+
"index",
|
|
423
|
+
"src/index"
|
|
424
|
+
];
|
|
340
425
|
/**
|
|
341
426
|
* Builds the client-side code.
|
|
342
427
|
*/
|
|
343
428
|
async function buildClient({ clientDir, watch, watchClient }, plugins) {
|
|
344
|
-
let clientEntries = await getEntryPoints(
|
|
345
|
-
"ui/index",
|
|
346
|
-
"client/index",
|
|
347
|
-
"index"
|
|
348
|
-
]);
|
|
429
|
+
let clientEntries = await getEntryPoints(entryClientPaths);
|
|
349
430
|
let buildOptions = {
|
|
350
431
|
...commonBuildOptions,
|
|
351
432
|
entryPoints: clientEntries.map(({ path }) => path),
|
|
@@ -391,12 +472,13 @@ ${content}
|
|
|
391
472
|
`);
|
|
392
473
|
}
|
|
393
474
|
|
|
475
|
+
const appServerEntryPoints = ["src/server/index.ts"];
|
|
394
476
|
async function buildServer(params, plugins) {
|
|
395
477
|
let { serverDir, watch, watchServer } = params;
|
|
396
478
|
await populateEntries(params);
|
|
397
479
|
let buildOptions = {
|
|
398
480
|
...commonBuildOptions,
|
|
399
|
-
entryPoints:
|
|
481
|
+
entryPoints: appServerEntryPoints,
|
|
400
482
|
bundle: true,
|
|
401
483
|
splitting: true,
|
|
402
484
|
outdir: `${serverDir}/server`,
|
|
@@ -415,8 +497,9 @@ async function buildServer(params, plugins) {
|
|
|
415
497
|
await esbuild.build(buildOptions);
|
|
416
498
|
}
|
|
417
499
|
|
|
500
|
+
const entryServerPaths = ["server", "server/index"];
|
|
418
501
|
async function buildServerCSS({ serverDir, watch, watchServer }, plugins) {
|
|
419
|
-
let serverEntries = await getEntryPoints(
|
|
502
|
+
let serverEntries = await getEntryPoints(entryServerPaths);
|
|
420
503
|
let buildOptions = {
|
|
421
504
|
...commonBuildOptions,
|
|
422
505
|
entryPoints: serverEntries.map(({ name, path }) => ({
|
|
@@ -603,4 +686,4 @@ function servePipeableStream(req, res) {
|
|
|
603
686
|
};
|
|
604
687
|
}
|
|
605
688
|
|
|
606
|
-
export { build, cli, createApp, cspNonce, dir, emitLog, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, renderStatus, requestEvents, resolveFilePath, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
|
689
|
+
export { build, cli, createApp, cspNonce, dir, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, renderStatus, requestEvents, resolveFilePath, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, join } from "node:path";
|
|
3
|
+
import type { Request } from "express";
|
|
4
|
+
import type { Controller } from "../types/Controller.ts";
|
|
5
|
+
import type { TransformContent } from "../types/TransformContent.ts";
|
|
6
|
+
import { emitLog } from "../utils/emitLog.ts";
|
|
7
|
+
|
|
8
|
+
const maxLanguages = 3;
|
|
9
|
+
|
|
10
|
+
async function resolve(...parts: string[]) {
|
|
11
|
+
let fullPath = join(...parts);
|
|
12
|
+
try {
|
|
13
|
+
if ((await lstat(fullPath)).isFile()) return fullPath;
|
|
14
|
+
} catch {}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ["en-US", "ru"] > ["en-US", "en", "ru"]
|
|
19
|
+
function getLanguageList(acceptedLanguages: string[]) {
|
|
20
|
+
let langs = new Set<string>();
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < acceptedLanguages.length && i < maxLanguages; i++) {
|
|
23
|
+
let s = acceptedLanguages[i];
|
|
24
|
+
let [lang] = s.split(/[-_]/);
|
|
25
|
+
|
|
26
|
+
if (s === lang) langs.add(s);
|
|
27
|
+
else {
|
|
28
|
+
langs.add(s);
|
|
29
|
+
langs.add(lang);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Array.from(langs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type FilesParams = {
|
|
37
|
+
base: string | string[];
|
|
38
|
+
path?: string | ((req: Request) => string);
|
|
39
|
+
extensions?: string[];
|
|
40
|
+
transform?: TransformContent[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const defaultExtensions = ["html", "htm"];
|
|
44
|
+
const defaultPath = (req: Request) => req.originalUrl.split("?")[0];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Serves files from the specified directory path in a locale-aware
|
|
48
|
+
* fashion after applying optional transforms.
|
|
49
|
+
*/
|
|
50
|
+
export const files: Controller<string | FilesParams> = (params) => {
|
|
51
|
+
let p: FilesParams = typeof params === "string" ? { base: params } : params;
|
|
52
|
+
|
|
53
|
+
let bases = Array.isArray(p.base) ? p.base : [p.base];
|
|
54
|
+
let exts = p.extensions ?? defaultExtensions;
|
|
55
|
+
|
|
56
|
+
return async (req, res) => {
|
|
57
|
+
let langs = getLanguageList(req.acceptsLanguages());
|
|
58
|
+
|
|
59
|
+
let path =
|
|
60
|
+
typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
|
|
61
|
+
|
|
62
|
+
if (path.includes("../")) {
|
|
63
|
+
emitLog(req.app, "Invalid path (potential traversal attempt)", {
|
|
64
|
+
data: { path },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
res.status(400).send(
|
|
68
|
+
await req.app.renderStatus?.(req, res, {
|
|
69
|
+
code: "invalid_path",
|
|
70
|
+
path,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// path: /x
|
|
78
|
+
// langs: en, ru
|
|
79
|
+
let filePath: string | null = null;
|
|
80
|
+
|
|
81
|
+
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
82
|
+
let base = bases[k];
|
|
83
|
+
|
|
84
|
+
// /x.en /x.ru
|
|
85
|
+
for (let i = 0; i < langs.length && filePath === null; i++)
|
|
86
|
+
filePath = await resolve(base, `${path}.${langs[i]}`);
|
|
87
|
+
|
|
88
|
+
// /x
|
|
89
|
+
if (filePath === null) filePath = await resolve(base, path);
|
|
90
|
+
|
|
91
|
+
// /x.en.html /x.en.htm /x.ru.html /x.ru.htm
|
|
92
|
+
for (let i = 0; i < langs.length && filePath === null; i++) {
|
|
93
|
+
for (let j = 0; j < exts.length && filePath === null; j++)
|
|
94
|
+
filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// /x.html /x.htm
|
|
98
|
+
for (let i = 0; i < exts.length && filePath === null; i++)
|
|
99
|
+
filePath = await resolve(base, `${path}.${exts[i]}`);
|
|
100
|
+
|
|
101
|
+
// /x.en/index.html /x.en/index.htm /x.ru/index.html /x.ru/index.htm
|
|
102
|
+
for (let i = 0; i < langs.length && filePath === null; i++) {
|
|
103
|
+
for (let j = 0; j < exts.length && filePath === null; j++)
|
|
104
|
+
filePath = await resolve(
|
|
105
|
+
base,
|
|
106
|
+
`${path}.${langs[i]}`,
|
|
107
|
+
`index.${exts[j]}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// /x/index.en.html /x/index.en.htm /x/index.ru.html /x/index.ru.htm
|
|
112
|
+
for (let i = 0; i < langs.length && filePath === null; i++) {
|
|
113
|
+
for (let j = 0; j < exts.length && filePath === null; j++)
|
|
114
|
+
filePath = await resolve(base, path, `index.${langs[i]}.${exts[j]}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// /x/index.html /x/index.htm
|
|
118
|
+
for (let i = 0; i < exts.length && filePath === null; i++)
|
|
119
|
+
filePath = await resolve(base, path, `index.${exts[i]}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (filePath === null) {
|
|
123
|
+
emitLog(req.app, "Unknown path", { data: { path } });
|
|
124
|
+
|
|
125
|
+
res.status(404).send(
|
|
126
|
+
await req.app.renderStatus?.(req, res, {
|
|
127
|
+
code: "unknown_path",
|
|
128
|
+
path,
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!p.transform?.length) {
|
|
136
|
+
res.sendFile(filePath);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let content = (await readFile(filePath)).toString();
|
|
141
|
+
let name = basename(filePath);
|
|
142
|
+
|
|
143
|
+
for (let transform of p.transform) {
|
|
144
|
+
let result = transform(req, res, { content, path, name });
|
|
145
|
+
|
|
146
|
+
content = result instanceof Promise ? await result : result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
res.type(extname(name).slice(1)).send(content);
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -3,6 +3,8 @@ import { commonBuildOptions } from "../const/commonBuildOptions.ts";
|
|
|
3
3
|
import type { BuildParams } from "../types/BuildParams.ts";
|
|
4
4
|
import { getEntryPoints } from "./getEntryPoints.ts";
|
|
5
5
|
|
|
6
|
+
const entryClientPaths = ["ui/index", "client/index", "index", "src/index"];
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Builds the client-side code.
|
|
8
10
|
*/
|
|
@@ -10,11 +12,7 @@ export async function buildClient(
|
|
|
10
12
|
{ clientDir, watch, watchClient }: BuildParams,
|
|
11
13
|
plugins?: Plugin[],
|
|
12
14
|
) {
|
|
13
|
-
let clientEntries = await getEntryPoints(
|
|
14
|
-
"ui/index",
|
|
15
|
-
"client/index",
|
|
16
|
-
"index",
|
|
17
|
-
]);
|
|
15
|
+
let clientEntries = await getEntryPoints(entryClientPaths);
|
|
18
16
|
|
|
19
17
|
let buildOptions: BuildOptions = {
|
|
20
18
|
...commonBuildOptions,
|
|
@@ -3,6 +3,8 @@ import { commonBuildOptions } from "../const/commonBuildOptions.ts";
|
|
|
3
3
|
import type { BuildParams } from "../types/BuildParams.ts";
|
|
4
4
|
import { populateEntries } from "./populateEntries.ts";
|
|
5
5
|
|
|
6
|
+
const appServerEntryPoints = ["src/server/index.ts"];
|
|
7
|
+
|
|
6
8
|
export async function buildServer(params: BuildParams, plugins?: Plugin[]) {
|
|
7
9
|
let { serverDir, watch, watchServer } = params;
|
|
8
10
|
|
|
@@ -10,7 +12,7 @@ export async function buildServer(params: BuildParams, plugins?: Plugin[]) {
|
|
|
10
12
|
|
|
11
13
|
let buildOptions: BuildOptions = {
|
|
12
14
|
...commonBuildOptions,
|
|
13
|
-
entryPoints:
|
|
15
|
+
entryPoints: appServerEntryPoints,
|
|
14
16
|
bundle: true,
|
|
15
17
|
splitting: true,
|
|
16
18
|
outdir: `${serverDir}/server`,
|
|
@@ -3,11 +3,13 @@ import { commonBuildOptions } from "../const/commonBuildOptions.ts";
|
|
|
3
3
|
import type { BuildParams } from "../types/BuildParams.ts";
|
|
4
4
|
import { getEntryPoints } from "./getEntryPoints.ts";
|
|
5
5
|
|
|
6
|
+
const entryServerPaths = ["server", "server/index"];
|
|
7
|
+
|
|
6
8
|
export async function buildServerCSS(
|
|
7
9
|
{ serverDir, watch, watchServer }: BuildParams,
|
|
8
10
|
plugins?: Plugin[],
|
|
9
11
|
) {
|
|
10
|
-
let serverEntries = await getEntryPoints(
|
|
12
|
+
let serverEntries = await getEntryPoints(entryServerPaths);
|
|
11
13
|
|
|
12
14
|
let buildOptions: BuildOptions = {
|
|
13
15
|
...commonBuildOptions,
|