appstage 0.2.21 → 0.2.23

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 CHANGED
@@ -79,7 +79,6 @@ function matches(x, matcher) {
79
79
  }
80
80
  const defaultExtensions = ["html", "htm"];
81
81
  const defaultPath = (req) => req.path;
82
- const defaultLanguages = getLanguageList;
83
82
  /**
84
83
  * Serves files from the specified directory path or paths in a locale-aware
85
84
  * fashion after applying optional transforms.
@@ -91,6 +90,7 @@ const files = (params) => {
91
90
  let fallthrough = p.fallthrough ?? true;
92
91
  return async (req, res, next) => {
93
92
  let urlPath = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
93
+ let defaultLanguage = typeof p.defaultLanguage === "function" ? p.defaultLanguage(req) : p.defaultLanguage ?? "en";
94
94
  if (!matches(urlPath, p.matches)) {
95
95
  if (fallthrough) next();
96
96
  else {
@@ -113,24 +113,25 @@ const files = (params) => {
113
113
  }
114
114
  return;
115
115
  }
116
- let langs = (p.languages ?? defaultLanguages)(req);
117
116
  let filePath = null;
118
117
  let urlExt = (0, node_path.extname)(urlPath);
118
+ let langs = (p.languages ?? getLanguageList)(req);
119
+ let defaultLanguageIndex = langs.indexOf(defaultLanguage);
120
+ let suffixes = langs.map((s) => `.${s}`);
121
+ if (defaultLanguageIndex === -1) suffixes.push("");
122
+ else suffixes.splice(defaultLanguageIndex + 1, 0, "");
119
123
  for (let k = 0; k < bases.length && filePath === null; k++) {
120
124
  let base = bases[k];
121
125
  if (!urlPath.endsWith("/")) {
122
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${urlPath}.${langs[i]}`);
126
+ for (let i = 0; i < suffixes.length && filePath === null; i++) filePath = await resolve(base, `${urlPath}${suffixes[i]}`);
123
127
  if (filePath === null && urlExt) {
124
128
  let urlPathBase = urlPath.slice(0, -urlExt.length);
125
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${urlPathBase}.${langs[i]}${urlExt}`);
129
+ for (let i = 0; i < suffixes.length && filePath === null; i++) filePath = await resolve(base, `${urlPathBase}${suffixes[i]}${urlExt}`);
126
130
  }
127
- if (filePath === null) filePath = await resolve(base, urlPath);
128
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${urlPath}.${langs[i]}.${exts[j]}`);
129
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${urlPath}.${exts[i]}`);
131
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${urlPath}${suffixes[i]}.${exts[j]}`);
130
132
  }
131
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${urlPath}.${langs[i]}`, `index.${exts[j]}`);
132
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, urlPath, `index.${langs[i]}.${exts[j]}`);
133
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, urlPath, `index.${exts[i]}`);
133
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, `${urlPath}${suffixes[i]}`, `index.${exts[j]}`);
134
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve(base, urlPath, `index${suffixes[i]}.${exts[j]}`);
134
135
  }
135
136
  if (filePath === null) {
136
137
  if (fallthrough) next();
@@ -162,6 +163,23 @@ const files = (params) => {
162
163
  };
163
164
  };
164
165
 
166
+ const redirect = (params) => {
167
+ let { url, status = 302 } = typeof params === "string" ? { url: params } : params;
168
+ return (req, res) => {
169
+ let search = req.originalUrl.split("?")[1] ?? "";
170
+ if (search) search = `${url.includes("?") ? "&" : "?"}${search}`;
171
+ emitLog(req.app, "Redirecting", {
172
+ data: {
173
+ from: req.originalUrl,
174
+ to: `${url}${search}`
175
+ },
176
+ req,
177
+ res
178
+ });
179
+ res.redirect(status, `${url}${search}`);
180
+ };
181
+ };
182
+
165
183
  const unhandledError = () => async (err, req, res) => {
166
184
  emitLog(req.app, "Unhandled error", {
167
185
  level: "error",
@@ -690,6 +708,7 @@ exports.init = init;
690
708
  exports.injectNonce = injectNonce;
691
709
  exports.lang = lang;
692
710
  exports.log = log;
711
+ exports.redirect = redirect;
693
712
  exports.renderStatus = renderStatus;
694
713
  exports.requestEvents = requestEvents;
695
714
  exports.serializeState = serializeState;
package/dist/index.d.ts CHANGED
@@ -21,8 +21,17 @@ type FilesParams = {
21
21
  base: string | string[];
22
22
  path?: string | ((req: Request) => string); /** Specifies which paths should be accepted. */
23
23
  matches?: StringMatcher;
24
+ /**
25
+ * @default ["html", "htm"]
26
+ */
24
27
  extensions?: string[];
25
28
  languages?: (req: Request) => string[];
29
+ /**
30
+ * Assumed file language if unspecified in the file name.
31
+ *
32
+ * @default "en"
33
+ */
34
+ defaultLanguage?: string | ((req: Request) => string);
26
35
  transform?: TransformContent[];
27
36
  fallthrough?: boolean;
28
37
  };
@@ -32,6 +41,12 @@ type FilesParams = {
32
41
  */
33
42
  declare const files: Controller<string | FilesParams>;
34
43
 
44
+ type RedirectParams = {
45
+ url: string;
46
+ status?: number;
47
+ };
48
+ declare const redirect: Controller<string | RedirectParams>;
49
+
35
50
  type ErrorController<T = void> = (params: T) => ErrorRequestHandler;
36
51
 
37
52
  declare const unhandledError: ErrorController;
@@ -1123,4 +1138,4 @@ declare function servePipeableStream(req: Request, res: Response): ({
1123
1138
  pipe
1124
1139
  }: PipeableStream, error?: unknown) => Promise<void>;
1125
1140
 
1126
- 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 };
1141
+ export { Controller, ErrorController, FilesParams, LangParams, LogEventPayload, LogLevel, LogOptions, Middleware, MiddlewareSet, RedirectParams, RenderStatus, ReqCtx, TransformContent, build, cli, createApp, cspNonce, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, redirect, renderStatus, requestEvents, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
package/dist/index.mjs CHANGED
@@ -53,7 +53,6 @@ function matches(x, matcher) {
53
53
  }
54
54
  const defaultExtensions = ["html", "htm"];
55
55
  const defaultPath = (req) => req.path;
56
- const defaultLanguages = getLanguageList;
57
56
  /**
58
57
  * Serves files from the specified directory path or paths in a locale-aware
59
58
  * fashion after applying optional transforms.
@@ -65,6 +64,7 @@ const files = (params) => {
65
64
  let fallthrough = p.fallthrough ?? true;
66
65
  return async (req, res, next) => {
67
66
  let urlPath = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
67
+ let defaultLanguage = typeof p.defaultLanguage === "function" ? p.defaultLanguage(req) : p.defaultLanguage ?? "en";
68
68
  if (!matches(urlPath, p.matches)) {
69
69
  if (fallthrough) next();
70
70
  else {
@@ -87,24 +87,25 @@ const files = (params) => {
87
87
  }
88
88
  return;
89
89
  }
90
- let langs = (p.languages ?? defaultLanguages)(req);
91
90
  let filePath = null;
92
91
  let urlExt = extname(urlPath);
92
+ let langs = (p.languages ?? getLanguageList)(req);
93
+ let defaultLanguageIndex = langs.indexOf(defaultLanguage);
94
+ let suffixes = langs.map((s) => `.${s}`);
95
+ if (defaultLanguageIndex === -1) suffixes.push("");
96
+ else suffixes.splice(defaultLanguageIndex + 1, 0, "");
93
97
  for (let k = 0; k < bases.length && filePath === null; k++) {
94
98
  let base = bases[k];
95
99
  if (!urlPath.endsWith("/")) {
96
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPath}.${langs[i]}`);
100
+ for (let i = 0; i < suffixes.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPath}${suffixes[i]}`);
97
101
  if (filePath === null && urlExt) {
98
102
  let urlPathBase = urlPath.slice(0, -urlExt.length);
99
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPathBase}.${langs[i]}${urlExt}`);
103
+ for (let i = 0; i < suffixes.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPathBase}${suffixes[i]}${urlExt}`);
100
104
  }
101
- if (filePath === null) filePath = await resolve$1(base, urlPath);
102
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${urlPath}.${langs[i]}.${exts[j]}`);
103
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPath}.${exts[i]}`);
105
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${urlPath}${suffixes[i]}.${exts[j]}`);
104
106
  }
105
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${urlPath}.${langs[i]}`, `index.${exts[j]}`);
106
- for (let i = 0; i < langs.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, urlPath, `index.${langs[i]}.${exts[j]}`);
107
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, urlPath, `index.${exts[i]}`);
107
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, `${urlPath}${suffixes[i]}`, `index.${exts[j]}`);
108
+ for (let i = 0; i < suffixes.length && filePath === null; i++) for (let j = 0; j < exts.length && filePath === null; j++) filePath = await resolve$1(base, urlPath, `index${suffixes[i]}.${exts[j]}`);
108
109
  }
109
110
  if (filePath === null) {
110
111
  if (fallthrough) next();
@@ -136,6 +137,23 @@ const files = (params) => {
136
137
  };
137
138
  };
138
139
 
140
+ const redirect = (params) => {
141
+ let { url, status = 302 } = typeof params === "string" ? { url: params } : params;
142
+ return (req, res) => {
143
+ let search = req.originalUrl.split("?")[1] ?? "";
144
+ if (search) search = `${url.includes("?") ? "&" : "?"}${search}`;
145
+ emitLog(req.app, "Redirecting", {
146
+ data: {
147
+ from: req.originalUrl,
148
+ to: `${url}${search}`
149
+ },
150
+ req,
151
+ res
152
+ });
153
+ res.redirect(status, `${url}${search}`);
154
+ };
155
+ };
156
+
139
157
  const unhandledError = () => async (err, req, res) => {
140
158
  emitLog(req.app, "Unhandled error", {
141
159
  level: "error",
@@ -651,4 +669,4 @@ function servePipeableStream(req, res) {
651
669
  };
652
670
  }
653
671
 
654
- export { build, cli, createApp, cspNonce, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, renderStatus, requestEvents, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
672
+ export { build, cli, createApp, cspNonce, emitLog, files, getEffectiveLocale, getLocales, getStatusMessage, init, injectNonce, lang, log, redirect, renderStatus, requestEvents, serializeState, servePipeableStream, toLanguage, unhandledError, unhandledRoute };
package/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./src/controllers/files.ts";
2
+ export * from "./src/controllers/redirect.ts";
2
3
  export * from "./src/controllers/unhandledError.ts";
3
4
  export * from "./src/controllers/unhandledRoute.ts";
4
5
  export * from "./src/lib/lang/getEffectiveLocale.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstage",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -66,15 +66,23 @@ export type FilesParams = {
66
66
  path?: string | ((req: Request) => string);
67
67
  /** Specifies which paths should be accepted. */
68
68
  matches?: StringMatcher;
69
+ /**
70
+ * @default ["html", "htm"]
71
+ */
69
72
  extensions?: string[];
70
73
  languages?: (req: Request) => string[];
74
+ /**
75
+ * Assumed file language if unspecified in the file name.
76
+ *
77
+ * @default "en"
78
+ */
79
+ defaultLanguage?: string | ((req: Request) => string);
71
80
  transform?: TransformContent[];
72
81
  fallthrough?: boolean;
73
82
  };
74
83
 
75
84
  const defaultExtensions = ["html", "htm"];
76
85
  const defaultPath = (req: Request) => req.path;
77
- const defaultLanguages = getLanguageList;
78
86
 
79
87
  /**
80
88
  * Serves files from the specified directory path or paths in a locale-aware
@@ -91,6 +99,11 @@ export const files: Controller<string | FilesParams> = (params) => {
91
99
  let urlPath =
92
100
  typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
93
101
 
102
+ let defaultLanguage =
103
+ typeof p.defaultLanguage === "function"
104
+ ? p.defaultLanguage(req)
105
+ : (p.defaultLanguage ?? "en");
106
+
94
107
  if (!matches(urlPath, p.matches)) {
95
108
  if (fallthrough) next();
96
109
  else {
@@ -125,67 +138,68 @@ export const files: Controller<string | FilesParams> = (params) => {
125
138
  return;
126
139
  }
127
140
 
128
- let langs = (p.languages ?? defaultLanguages)(req);
129
141
  let filePath: string | null = null;
130
142
  let urlExt = extname(urlPath);
131
143
 
132
- // Example: path = /x, langs = [en, ru], exts = [html, htm]
144
+ let langs = (p.languages ?? getLanguageList)(req);
145
+ let defaultLanguageIndex = langs.indexOf(defaultLanguage);
146
+
147
+ let suffixes = langs.map((s) => `.${s}`);
148
+
149
+ // Check the suffixless path right after the default language suffix.
150
+ if (defaultLanguageIndex === -1) suffixes.push("");
151
+ else suffixes.splice(defaultLanguageIndex + 1, 0, "");
152
+
153
+ // Example:
154
+ // path = /x, langs = [en, ru], default lang: en, exts = [html, htm]
133
155
  for (let k = 0; k < bases.length && filePath === null; k++) {
134
156
  let base = bases[k];
135
157
 
136
158
  if (!urlPath.endsWith("/")) {
137
- // /x.en /x.ru
138
- for (let i = 0; i < langs.length && filePath === null; i++)
139
- filePath = await resolve(base, `${urlPath}.${langs[i]}`);
159
+ // /x.en /x /x.ru
160
+ for (let i = 0; i < suffixes.length && filePath === null; i++)
161
+ filePath = await resolve(base, `${urlPath}${suffixes[i]}`);
140
162
 
141
163
  if (filePath === null && urlExt) {
142
164
  let urlPathBase = urlPath.slice(0, -urlExt.length);
143
165
 
144
- // /x.en.ext /x.ru.ext
145
- for (let i = 0; i < langs.length && filePath === null; i++)
166
+ // /x.en.ext /x.ext /x.ru.ext
167
+ for (let i = 0; i < suffixes.length && filePath === null; i++)
146
168
  filePath = await resolve(
147
169
  base,
148
- `${urlPathBase}.${langs[i]}${urlExt}`,
170
+ `${urlPathBase}${suffixes[i]}${urlExt}`,
149
171
  );
150
172
  }
151
173
 
152
- // /x
153
- if (filePath === null) filePath = await resolve(base, urlPath);
154
-
155
- // /x.en.html /x.en.htm /x.ru.html /x.ru.htm
156
- for (let i = 0; i < langs.length && filePath === null; i++) {
174
+ // /x.en.html /x.en.htm /x.html /x.htm /x.ru.html /x.ru.htm
175
+ for (let i = 0; i < suffixes.length && filePath === null; i++) {
157
176
  for (let j = 0; j < exts.length && filePath === null; j++)
158
- filePath = await resolve(base, `${urlPath}.${langs[i]}.${exts[j]}`);
177
+ filePath = await resolve(
178
+ base,
179
+ `${urlPath}${suffixes[i]}.${exts[j]}`,
180
+ );
159
181
  }
160
-
161
- // /x.html /x.htm
162
- for (let i = 0; i < exts.length && filePath === null; i++)
163
- filePath = await resolve(base, `${urlPath}.${exts[i]}`);
164
182
  }
165
183
 
166
- // /x.en/index.html /x.en/index.htm /x.ru/index.html /x.ru/index.htm
167
- for (let i = 0; i < langs.length && filePath === null; i++) {
184
+ // /x.en/index.html /x.en/index.htm /x/index.html /x/index.htm /x.ru/index.html /x.ru/index.htm
185
+ for (let i = 0; i < suffixes.length && filePath === null; i++) {
168
186
  for (let j = 0; j < exts.length && filePath === null; j++)
169
187
  filePath = await resolve(
170
188
  base,
171
- `${urlPath}.${langs[i]}`,
189
+ `${urlPath}${suffixes[i]}`,
172
190
  `index.${exts[j]}`,
173
191
  );
174
192
  }
175
193
 
176
- // /x/index.en.html /x/index.en.htm /x/index.ru.html /x/index.ru.htm
177
- for (let i = 0; i < langs.length && filePath === null; i++) {
194
+ // /x/index.en.html /x/index.en.htm /x/index.html /x/index.htm /x/index.ru.html /x/index.ru.htm
195
+ for (let i = 0; i < suffixes.length && filePath === null; i++) {
178
196
  for (let j = 0; j < exts.length && filePath === null; j++)
179
197
  filePath = await resolve(
180
198
  base,
181
199
  urlPath,
182
- `index.${langs[i]}.${exts[j]}`,
200
+ `index${suffixes[i]}.${exts[j]}`,
183
201
  );
184
202
  }
185
-
186
- // /x/index.html /x/index.htm
187
- for (let i = 0; i < exts.length && filePath === null; i++)
188
- filePath = await resolve(base, urlPath, `index.${exts[i]}`);
189
203
  }
190
204
 
191
205
  if (filePath === null) {
@@ -1,4 +1,5 @@
1
1
  import type { Controller } from "../types/Controller.ts";
2
+ import { emitLog } from "../utils/emitLog.ts";
2
3
 
3
4
  export type RedirectParams = {
4
5
  url: string;
@@ -14,6 +15,15 @@ export const redirect: Controller<string | RedirectParams> = (params) => {
14
15
 
15
16
  if (search) search = `${url.includes("?") ? "&" : "?"}${search}`;
16
17
 
18
+ emitLog(req.app, "Redirecting", {
19
+ data: {
20
+ from: req.originalUrl,
21
+ to: `${url}${search}`,
22
+ },
23
+ req,
24
+ res,
25
+ });
26
+
17
27
  res.redirect(status, `${url}${search}`);
18
28
  };
19
29
  };