appstage 0.2.10 → 0.2.11
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/index.cjs +8 -85
- package/dist/index.d.ts +2 -61
- package/dist/index.mjs +9 -84
- package/index.ts +0 -2
- package/package.json +1 -1
- package/src/controllers/files.ts +6 -5
- package/src/controllers/dir.ts +0 -119
- package/src/utils/resolveFilePath.ts +0 -78
package/dist/index.cjs
CHANGED
|
@@ -44,86 +44,6 @@ function emitLog(app, message, payload) {
|
|
|
44
44
|
return app.events?.emit("log", normalizedPayload);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function toLanguage(locale) {
|
|
48
|
-
return locale.split(/[-_]/)[0];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function resolveFilePath({ name, dir = ".", lang, supportedLocales = [], ext, index }) {
|
|
52
|
-
let cwd = process.cwd();
|
|
53
|
-
let localeSet = new Set(supportedLocales);
|
|
54
|
-
let langSet = new Set(supportedLocales.map(toLanguage));
|
|
55
|
-
let availableNames = [name, ...[...localeSet, ...langSet].map((item) => `${name}.${item}`)];
|
|
56
|
-
let preferredLangNames;
|
|
57
|
-
if (lang && (!supportedLocales.length || localeSet.has(lang) || langSet.has(lang))) preferredLangNames = [`${name}.${lang}`, `${name}.${toLanguage(lang)}`];
|
|
58
|
-
let names = new Set(preferredLangNames ? [...preferredLangNames, ...availableNames] : availableNames);
|
|
59
|
-
let exts = Array.isArray(ext) ? ext : [ext];
|
|
60
|
-
for (let item of names) for (let itemExt of exts) {
|
|
61
|
-
let path = (0, node_path.join)(cwd, dir, `${item}${itemExt ? `.${itemExt}` : ""}`);
|
|
62
|
-
try {
|
|
63
|
-
await (0, node_fs_promises.access)(path);
|
|
64
|
-
return path;
|
|
65
|
-
} catch {}
|
|
66
|
-
}
|
|
67
|
-
if (index) for (let item of names) for (let itemExt of exts) {
|
|
68
|
-
let path = (0, node_path.join)(cwd, dir, item, `index${itemExt ? `.${itemExt}` : ""}`);
|
|
69
|
-
try {
|
|
70
|
-
await (0, node_fs_promises.access)(path);
|
|
71
|
-
return path;
|
|
72
|
-
} catch {}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const defaultExt = ["html", "htm"];
|
|
77
|
-
const defaultName = (req) => req.path.split("/").at(-1);
|
|
78
|
-
/**
|
|
79
|
-
* Serves files from the specified directory path in a locale-aware
|
|
80
|
-
* fashion after applying optional transforms.
|
|
81
|
-
*
|
|
82
|
-
* A file ending with `.<lang>.<ext>` is picked first if the `<lang>`
|
|
83
|
-
* part matches `req.ctx.lang`. If the `supportedLocales` array is
|
|
84
|
-
* provided, the `*.<lang>.<ext>` file is picked only if the given
|
|
85
|
-
* array contains `req.ctx.lang`. Otherwise, a file without the locale
|
|
86
|
-
* in its path (`*.<ext>`) is picked.
|
|
87
|
-
*/
|
|
88
|
-
const dir = ({ path, name = defaultName, ext = defaultExt, transform, supportedLocales, index = true }) => {
|
|
89
|
-
if (typeof path !== "string") throw new Error(`'path' is not a string`);
|
|
90
|
-
let transformSet = (Array.isArray(transform) ? transform : [transform]).filter((item) => typeof item === "function");
|
|
91
|
-
return async (req, res) => {
|
|
92
|
-
let fileName = typeof name === "function" ? name(req, res) : name;
|
|
93
|
-
emitLog(req.app, `Name: ${JSON.stringify(fileName)}`, {
|
|
94
|
-
req,
|
|
95
|
-
res
|
|
96
|
-
});
|
|
97
|
-
if (fileName === void 0) {
|
|
98
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
let filePath = await resolveFilePath({
|
|
102
|
-
name: fileName,
|
|
103
|
-
dir: path,
|
|
104
|
-
ext,
|
|
105
|
-
supportedLocales,
|
|
106
|
-
lang: req.ctx?.lang,
|
|
107
|
-
index
|
|
108
|
-
});
|
|
109
|
-
emitLog(req.app, `Path: ${JSON.stringify(filePath)}`, {
|
|
110
|
-
req,
|
|
111
|
-
res
|
|
112
|
-
});
|
|
113
|
-
if (!filePath) {
|
|
114
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
let content = (await (0, node_fs_promises.readFile)(filePath)).toString();
|
|
118
|
-
for (let transformItem of transformSet) content = await transformItem(req, res, {
|
|
119
|
-
content,
|
|
120
|
-
path: filePath,
|
|
121
|
-
name: (0, node_path.basename)(filePath, (0, node_path.extname)(filePath))
|
|
122
|
-
});
|
|
123
|
-
res.send(content);
|
|
124
|
-
};
|
|
125
|
-
};
|
|
126
|
-
|
|
127
47
|
const maxLanguages = 3;
|
|
128
48
|
async function resolve(...parts) {
|
|
129
49
|
let fullPath = (0, node_path.join)(...parts);
|
|
@@ -194,16 +114,17 @@ const files = (params) => {
|
|
|
194
114
|
return;
|
|
195
115
|
}
|
|
196
116
|
let content = (await (0, node_fs_promises.readFile)(filePath)).toString();
|
|
197
|
-
let
|
|
117
|
+
let ext = (0, node_path.extname)(filePath);
|
|
118
|
+
let name = (0, node_path.basename)(filePath, ext);
|
|
198
119
|
for (let transform of p.transform) {
|
|
199
120
|
let result = transform(req, res, {
|
|
200
121
|
content,
|
|
201
|
-
path,
|
|
122
|
+
path: filePath,
|
|
202
123
|
name
|
|
203
124
|
});
|
|
204
125
|
content = result instanceof Promise ? await result : result;
|
|
205
126
|
}
|
|
206
|
-
res.type(
|
|
127
|
+
res.type(ext.slice(1)).send(content);
|
|
207
128
|
};
|
|
208
129
|
};
|
|
209
130
|
|
|
@@ -226,6 +147,10 @@ const unhandledRoute = () => async (req, res) => {
|
|
|
226
147
|
res.status(404).send(await req.app.renderStatus?.(req, res, "unhandled_route"));
|
|
227
148
|
};
|
|
228
149
|
|
|
150
|
+
function toLanguage(locale) {
|
|
151
|
+
return locale.split(/[-_]/)[0];
|
|
152
|
+
}
|
|
153
|
+
|
|
229
154
|
function getEffectiveLocale(preferredLocales, supportedLocales) {
|
|
230
155
|
if (!supportedLocales || supportedLocales.length === 0) return void 0;
|
|
231
156
|
if (!preferredLocales || preferredLocales.length === 0) return supportedLocales[0];
|
|
@@ -720,7 +645,6 @@ exports.build = build;
|
|
|
720
645
|
exports.cli = cli;
|
|
721
646
|
exports.createApp = createApp;
|
|
722
647
|
exports.cspNonce = cspNonce;
|
|
723
|
-
exports.dir = dir;
|
|
724
648
|
exports.emitLog = emitLog;
|
|
725
649
|
exports.files = files;
|
|
726
650
|
exports.getEffectiveLocale = getEffectiveLocale;
|
|
@@ -732,7 +656,6 @@ exports.lang = lang;
|
|
|
732
656
|
exports.log = log;
|
|
733
657
|
exports.renderStatus = renderStatus;
|
|
734
658
|
exports.requestEvents = requestEvents;
|
|
735
|
-
exports.resolveFilePath = resolveFilePath;
|
|
736
659
|
exports.serializeState = serializeState;
|
|
737
660
|
exports.servePipeableStream = servePipeableStream;
|
|
738
661
|
exports.toLanguage = toLanguage;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,65 +16,6 @@ type TransformContent = (req: Request, res: Response, params: {
|
|
|
16
16
|
name?: string;
|
|
17
17
|
}) => string | Promise<string>;
|
|
18
18
|
|
|
19
|
-
type ResolveFilePathParams = {
|
|
20
|
-
name: string;
|
|
21
|
-
dir?: string;
|
|
22
|
-
lang?: string;
|
|
23
|
-
supportedLocales?: string[]; /** Allowed file extensions. */
|
|
24
|
-
ext?: string | string[];
|
|
25
|
-
/**
|
|
26
|
-
* Whether an index file should be checked if the resolved file name
|
|
27
|
-
* doesn't correspond to an existing file.
|
|
28
|
-
*
|
|
29
|
-
* @defaultValue `true`
|
|
30
|
-
*/
|
|
31
|
-
index?: boolean;
|
|
32
|
-
};
|
|
33
|
-
declare function resolveFilePath({
|
|
34
|
-
name,
|
|
35
|
-
dir,
|
|
36
|
-
lang,
|
|
37
|
-
supportedLocales,
|
|
38
|
-
ext,
|
|
39
|
-
index
|
|
40
|
-
}: ResolveFilePathParams): Promise<string | undefined>;
|
|
41
|
-
|
|
42
|
-
type ZeroTransform = false | null | undefined;
|
|
43
|
-
type DirParams = Partial<Pick<ResolveFilePathParams, "supportedLocales" | "index">> & {
|
|
44
|
-
/** Directory path to serve files from. */path: string;
|
|
45
|
-
/**
|
|
46
|
-
* File name.
|
|
47
|
-
* By default, the portion of `req.path` after the last slash.
|
|
48
|
-
*/
|
|
49
|
-
name?: string | undefined | ((req: Request, res: Response) => string | undefined);
|
|
50
|
-
/**
|
|
51
|
-
* Allowed file extensions.
|
|
52
|
-
*
|
|
53
|
-
* @defaultValue `['html', 'htm']`
|
|
54
|
-
*/
|
|
55
|
-
ext?: ResolveFilePathParams["ext"];
|
|
56
|
-
/**
|
|
57
|
-
* Custom transforms applied to the file content.
|
|
58
|
-
*
|
|
59
|
-
* Example: Use `injectNonce` from this package to inject the `nonce`
|
|
60
|
-
* value generated for the current request into the `{{nonce}}`
|
|
61
|
-
* placeholders in an HTML file.
|
|
62
|
-
*/
|
|
63
|
-
transform?: TransformContent | ZeroTransform | (TransformContent | ZeroTransform)[];
|
|
64
|
-
supportedLocales?: string[];
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Serves files from the specified directory path in a locale-aware
|
|
68
|
-
* fashion after applying optional transforms.
|
|
69
|
-
*
|
|
70
|
-
* A file ending with `.<lang>.<ext>` is picked first if the `<lang>`
|
|
71
|
-
* part matches `req.ctx.lang`. If the `supportedLocales` array is
|
|
72
|
-
* provided, the `*.<lang>.<ext>` file is picked only if the given
|
|
73
|
-
* array contains `req.ctx.lang`. Otherwise, a file without the locale
|
|
74
|
-
* in its path (`*.<ext>`) is picked.
|
|
75
|
-
*/
|
|
76
|
-
declare const dir$1: Controller<DirParams>;
|
|
77
|
-
|
|
78
19
|
type FilesParams = {
|
|
79
20
|
base: string | string[];
|
|
80
21
|
path?: string | ((req: Request) => string);
|
|
@@ -136,7 +77,7 @@ type LangParams = {
|
|
|
136
77
|
shouldRedirect?: boolean;
|
|
137
78
|
langCookieOptions?: CookieOptions;
|
|
138
79
|
};
|
|
139
|
-
declare const lang
|
|
80
|
+
declare const lang: Middleware<LangParams | void>;
|
|
140
81
|
|
|
141
82
|
/**
|
|
142
83
|
* Adds event handlers, like logging, to essential request phases.
|
|
@@ -1179,4 +1120,4 @@ declare function servePipeableStream(req: Request, res: Response): ({
|
|
|
1179
1120
|
pipe
|
|
1180
1121
|
}: PipeableStream, error?: unknown) => Promise<void>;
|
|
1181
1122
|
|
|
1182
|
-
export { Controller,
|
|
1123
|
+
export { Controller, ErrorController, FilesParams, LangParams, LogEventPayload, LogLevel, LogOptions, Middleware, MiddlewareSet, RenderStatus, ReqCtx, TransformContent, build, cli, createApp, cspNonce, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, renderStatus, requestEvents, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
package/dist/index.mjs
CHANGED
|
@@ -18,86 +18,6 @@ function emitLog(app, message, payload) {
|
|
|
18
18
|
return app.events?.emit("log", normalizedPayload);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function toLanguage(locale) {
|
|
22
|
-
return locale.split(/[-_]/)[0];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function resolveFilePath({ name, dir = ".", lang, supportedLocales = [], ext, index }) {
|
|
26
|
-
let cwd = process.cwd();
|
|
27
|
-
let localeSet = new Set(supportedLocales);
|
|
28
|
-
let langSet = new Set(supportedLocales.map(toLanguage));
|
|
29
|
-
let availableNames = [name, ...[...localeSet, ...langSet].map((item) => `${name}.${item}`)];
|
|
30
|
-
let preferredLangNames;
|
|
31
|
-
if (lang && (!supportedLocales.length || localeSet.has(lang) || langSet.has(lang))) preferredLangNames = [`${name}.${lang}`, `${name}.${toLanguage(lang)}`];
|
|
32
|
-
let names = new Set(preferredLangNames ? [...preferredLangNames, ...availableNames] : availableNames);
|
|
33
|
-
let exts = Array.isArray(ext) ? ext : [ext];
|
|
34
|
-
for (let item of names) for (let itemExt of exts) {
|
|
35
|
-
let path = join(cwd, dir, `${item}${itemExt ? `.${itemExt}` : ""}`);
|
|
36
|
-
try {
|
|
37
|
-
await access(path);
|
|
38
|
-
return path;
|
|
39
|
-
} catch {}
|
|
40
|
-
}
|
|
41
|
-
if (index) for (let item of names) for (let itemExt of exts) {
|
|
42
|
-
let path = join(cwd, dir, item, `index${itemExt ? `.${itemExt}` : ""}`);
|
|
43
|
-
try {
|
|
44
|
-
await access(path);
|
|
45
|
-
return path;
|
|
46
|
-
} catch {}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const defaultExt = ["html", "htm"];
|
|
51
|
-
const defaultName = (req) => req.path.split("/").at(-1);
|
|
52
|
-
/**
|
|
53
|
-
* Serves files from the specified directory path in a locale-aware
|
|
54
|
-
* fashion after applying optional transforms.
|
|
55
|
-
*
|
|
56
|
-
* A file ending with `.<lang>.<ext>` is picked first if the `<lang>`
|
|
57
|
-
* part matches `req.ctx.lang`. If the `supportedLocales` array is
|
|
58
|
-
* provided, the `*.<lang>.<ext>` file is picked only if the given
|
|
59
|
-
* array contains `req.ctx.lang`. Otherwise, a file without the locale
|
|
60
|
-
* in its path (`*.<ext>`) is picked.
|
|
61
|
-
*/
|
|
62
|
-
const dir = ({ path, name = defaultName, ext = defaultExt, transform, supportedLocales, index = true }) => {
|
|
63
|
-
if (typeof path !== "string") throw new Error(`'path' is not a string`);
|
|
64
|
-
let transformSet = (Array.isArray(transform) ? transform : [transform]).filter((item) => typeof item === "function");
|
|
65
|
-
return async (req, res) => {
|
|
66
|
-
let fileName = typeof name === "function" ? name(req, res) : name;
|
|
67
|
-
emitLog(req.app, `Name: ${JSON.stringify(fileName)}`, {
|
|
68
|
-
req,
|
|
69
|
-
res
|
|
70
|
-
});
|
|
71
|
-
if (fileName === void 0) {
|
|
72
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
let filePath = await resolveFilePath({
|
|
76
|
-
name: fileName,
|
|
77
|
-
dir: path,
|
|
78
|
-
ext,
|
|
79
|
-
supportedLocales,
|
|
80
|
-
lang: req.ctx?.lang,
|
|
81
|
-
index
|
|
82
|
-
});
|
|
83
|
-
emitLog(req.app, `Path: ${JSON.stringify(filePath)}`, {
|
|
84
|
-
req,
|
|
85
|
-
res
|
|
86
|
-
});
|
|
87
|
-
if (!filePath) {
|
|
88
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
let content = (await readFile(filePath)).toString();
|
|
92
|
-
for (let transformItem of transformSet) content = await transformItem(req, res, {
|
|
93
|
-
content,
|
|
94
|
-
path: filePath,
|
|
95
|
-
name: basename(filePath, extname(filePath))
|
|
96
|
-
});
|
|
97
|
-
res.send(content);
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
|
|
101
21
|
const maxLanguages = 3;
|
|
102
22
|
async function resolve(...parts) {
|
|
103
23
|
let fullPath = join(...parts);
|
|
@@ -168,16 +88,17 @@ const files = (params) => {
|
|
|
168
88
|
return;
|
|
169
89
|
}
|
|
170
90
|
let content = (await readFile(filePath)).toString();
|
|
171
|
-
let
|
|
91
|
+
let ext = extname(filePath);
|
|
92
|
+
let name = basename(filePath, ext);
|
|
172
93
|
for (let transform of p.transform) {
|
|
173
94
|
let result = transform(req, res, {
|
|
174
95
|
content,
|
|
175
|
-
path,
|
|
96
|
+
path: filePath,
|
|
176
97
|
name
|
|
177
98
|
});
|
|
178
99
|
content = result instanceof Promise ? await result : result;
|
|
179
100
|
}
|
|
180
|
-
res.type(
|
|
101
|
+
res.type(ext.slice(1)).send(content);
|
|
181
102
|
};
|
|
182
103
|
};
|
|
183
104
|
|
|
@@ -200,6 +121,10 @@ const unhandledRoute = () => async (req, res) => {
|
|
|
200
121
|
res.status(404).send(await req.app.renderStatus?.(req, res, "unhandled_route"));
|
|
201
122
|
};
|
|
202
123
|
|
|
124
|
+
function toLanguage(locale) {
|
|
125
|
+
return locale.split(/[-_]/)[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
203
128
|
function getEffectiveLocale(preferredLocales, supportedLocales) {
|
|
204
129
|
if (!supportedLocales || supportedLocales.length === 0) return void 0;
|
|
205
130
|
if (!preferredLocales || preferredLocales.length === 0) return supportedLocales[0];
|
|
@@ -690,4 +615,4 @@ function servePipeableStream(req, res) {
|
|
|
690
615
|
};
|
|
691
616
|
}
|
|
692
617
|
|
|
693
|
-
export { build, cli, createApp, cspNonce,
|
|
618
|
+
export { build, cli, createApp, cspNonce, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, renderStatus, requestEvents, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
|
package/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export * from "./src/controllers/dir.ts";
|
|
2
1
|
export * from "./src/controllers/files.ts";
|
|
3
2
|
export * from "./src/controllers/unhandledError.ts";
|
|
4
3
|
export * from "./src/controllers/unhandledRoute.ts";
|
|
@@ -27,6 +26,5 @@ export * from "./src/utils/emitLog.ts";
|
|
|
27
26
|
export * from "./src/utils/getStatusMessage.ts";
|
|
28
27
|
export * from "./src/utils/injectNonce.ts";
|
|
29
28
|
export * from "./src/utils/renderStatus.ts";
|
|
30
|
-
export * from "./src/utils/resolveFilePath.ts";
|
|
31
29
|
export * from "./src/utils/serializeState.ts";
|
|
32
30
|
export * from "./src/utils/servePipeableStream.ts";
|
package/package.json
CHANGED
package/src/controllers/files.ts
CHANGED
|
@@ -81,10 +81,10 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// path: /x
|
|
85
|
-
// langs: en, ru
|
|
86
84
|
let filePath: string | null = null;
|
|
87
85
|
|
|
86
|
+
// path: /x
|
|
87
|
+
// langs: en, ru
|
|
88
88
|
for (let k = 0; k < bases.length && filePath === null; k++) {
|
|
89
89
|
let base = bases[k];
|
|
90
90
|
|
|
@@ -145,14 +145,15 @@ export const files: Controller<string | FilesParams> = (params) => {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
let content = (await readFile(filePath)).toString();
|
|
148
|
-
let
|
|
148
|
+
let ext = extname(filePath);
|
|
149
|
+
let name = basename(filePath, ext);
|
|
149
150
|
|
|
150
151
|
for (let transform of p.transform) {
|
|
151
|
-
let result = transform(req, res, { content, path, name });
|
|
152
|
+
let result = transform(req, res, { content, path: filePath, name });
|
|
152
153
|
|
|
153
154
|
content = result instanceof Promise ? await result : result;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
res.type(
|
|
157
|
+
res.type(ext.slice(1)).send(content);
|
|
157
158
|
};
|
|
158
159
|
};
|
package/src/controllers/dir.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { basename, extname } from "node:path";
|
|
3
|
-
import type { Request, Response } 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
|
-
import {
|
|
8
|
-
type ResolveFilePathParams,
|
|
9
|
-
resolveFilePath,
|
|
10
|
-
} from "../utils/resolveFilePath.ts";
|
|
11
|
-
|
|
12
|
-
const defaultExt = ["html", "htm"];
|
|
13
|
-
const defaultName = (req: Request) => req.path.split("/").at(-1);
|
|
14
|
-
|
|
15
|
-
type ZeroTransform = false | null | undefined;
|
|
16
|
-
|
|
17
|
-
export type DirParams = Partial<
|
|
18
|
-
Pick<ResolveFilePathParams, "supportedLocales" | "index">
|
|
19
|
-
> & {
|
|
20
|
-
/** Directory path to serve files from. */
|
|
21
|
-
path: string;
|
|
22
|
-
/**
|
|
23
|
-
* File name.
|
|
24
|
-
* By default, the portion of `req.path` after the last slash.
|
|
25
|
-
*/
|
|
26
|
-
name?:
|
|
27
|
-
| string
|
|
28
|
-
| undefined
|
|
29
|
-
| ((req: Request, res: Response) => string | undefined);
|
|
30
|
-
/**
|
|
31
|
-
* Allowed file extensions.
|
|
32
|
-
*
|
|
33
|
-
* @defaultValue `['html', 'htm']`
|
|
34
|
-
*/
|
|
35
|
-
ext?: ResolveFilePathParams["ext"];
|
|
36
|
-
/**
|
|
37
|
-
* Custom transforms applied to the file content.
|
|
38
|
-
*
|
|
39
|
-
* Example: Use `injectNonce` from this package to inject the `nonce`
|
|
40
|
-
* value generated for the current request into the `{{nonce}}`
|
|
41
|
-
* placeholders in an HTML file.
|
|
42
|
-
*/
|
|
43
|
-
transform?:
|
|
44
|
-
| TransformContent
|
|
45
|
-
| ZeroTransform
|
|
46
|
-
| (TransformContent | ZeroTransform)[];
|
|
47
|
-
supportedLocales?: string[];
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Serves files from the specified directory path in a locale-aware
|
|
52
|
-
* fashion after applying optional transforms.
|
|
53
|
-
*
|
|
54
|
-
* A file ending with `.<lang>.<ext>` is picked first if the `<lang>`
|
|
55
|
-
* part matches `req.ctx.lang`. If the `supportedLocales` array is
|
|
56
|
-
* provided, the `*.<lang>.<ext>` file is picked only if the given
|
|
57
|
-
* array contains `req.ctx.lang`. Otherwise, a file without the locale
|
|
58
|
-
* in its path (`*.<ext>`) is picked.
|
|
59
|
-
*/
|
|
60
|
-
export const dir: Controller<DirParams> = ({
|
|
61
|
-
path,
|
|
62
|
-
name = defaultName,
|
|
63
|
-
ext = defaultExt,
|
|
64
|
-
transform,
|
|
65
|
-
supportedLocales,
|
|
66
|
-
index = true,
|
|
67
|
-
}) => {
|
|
68
|
-
if (typeof path !== "string") throw new Error(`'path' is not a string`);
|
|
69
|
-
|
|
70
|
-
let transformSet = (
|
|
71
|
-
Array.isArray(transform) ? transform : [transform]
|
|
72
|
-
).filter((item) => typeof item === "function");
|
|
73
|
-
|
|
74
|
-
return async (req, res) => {
|
|
75
|
-
let fileName = typeof name === "function" ? name(req, res) : name;
|
|
76
|
-
|
|
77
|
-
emitLog(req.app, `Name: ${JSON.stringify(fileName)}`, {
|
|
78
|
-
req,
|
|
79
|
-
res,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (fileName === undefined) {
|
|
83
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let filePath = await resolveFilePath({
|
|
89
|
-
name: fileName,
|
|
90
|
-
dir: path,
|
|
91
|
-
ext,
|
|
92
|
-
supportedLocales,
|
|
93
|
-
lang: req.ctx?.lang,
|
|
94
|
-
index,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
emitLog(req.app, `Path: ${JSON.stringify(filePath)}`, {
|
|
98
|
-
req,
|
|
99
|
-
res,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (!filePath) {
|
|
103
|
-
res.status(404).send(await req.app.renderStatus?.(req, res));
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let content = (await readFile(filePath)).toString();
|
|
109
|
-
|
|
110
|
-
for (let transformItem of transformSet)
|
|
111
|
-
content = await transformItem(req, res, {
|
|
112
|
-
content,
|
|
113
|
-
path: filePath,
|
|
114
|
-
name: basename(filePath, extname(filePath)),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
res.send(content);
|
|
118
|
-
};
|
|
119
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { toLanguage } from "../lib/lang/toLanguage.ts";
|
|
4
|
-
|
|
5
|
-
export type ResolveFilePathParams = {
|
|
6
|
-
name: string;
|
|
7
|
-
dir?: string;
|
|
8
|
-
lang?: string;
|
|
9
|
-
supportedLocales?: string[];
|
|
10
|
-
/** Allowed file extensions. */
|
|
11
|
-
ext?: string | string[];
|
|
12
|
-
/**
|
|
13
|
-
* Whether an index file should be checked if the resolved file name
|
|
14
|
-
* doesn't correspond to an existing file.
|
|
15
|
-
*
|
|
16
|
-
* @defaultValue `true`
|
|
17
|
-
*/
|
|
18
|
-
index?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export async function resolveFilePath({
|
|
22
|
-
name,
|
|
23
|
-
dir = ".",
|
|
24
|
-
lang,
|
|
25
|
-
supportedLocales = [],
|
|
26
|
-
ext,
|
|
27
|
-
index,
|
|
28
|
-
}: ResolveFilePathParams) {
|
|
29
|
-
let cwd = process.cwd();
|
|
30
|
-
|
|
31
|
-
let localeSet = new Set(supportedLocales);
|
|
32
|
-
let langSet = new Set(supportedLocales.map(toLanguage));
|
|
33
|
-
|
|
34
|
-
let availableNames = [
|
|
35
|
-
name,
|
|
36
|
-
...[...localeSet, ...langSet].map((item) => `${name}.${item}`),
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
let preferredLangNames: string[] | undefined;
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
lang &&
|
|
43
|
-
(!supportedLocales.length || localeSet.has(lang) || langSet.has(lang))
|
|
44
|
-
)
|
|
45
|
-
preferredLangNames = [`${name}.${lang}`, `${name}.${toLanguage(lang)}`];
|
|
46
|
-
|
|
47
|
-
let names = new Set(
|
|
48
|
-
preferredLangNames
|
|
49
|
-
? [...preferredLangNames, ...availableNames]
|
|
50
|
-
: availableNames,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
let exts = Array.isArray(ext) ? ext : [ext];
|
|
54
|
-
|
|
55
|
-
for (let item of names) {
|
|
56
|
-
for (let itemExt of exts) {
|
|
57
|
-
let path = join(cwd, dir, `${item}${itemExt ? `.${itemExt}` : ""}`);
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
await access(path);
|
|
61
|
-
return path;
|
|
62
|
-
} catch {}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (index) {
|
|
67
|
-
for (let item of names) {
|
|
68
|
-
for (let itemExt of exts) {
|
|
69
|
-
let path = join(cwd, dir, item, `index${itemExt ? `.${itemExt}` : ""}`);
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
await access(path);
|
|
73
|
-
return path;
|
|
74
|
-
} catch {}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|