appstage 0.2.18 → 0.2.20

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
@@ -81,7 +81,7 @@ const defaultExtensions = ["html", "htm"];
81
81
  const defaultPath = (req) => req.path;
82
82
  const defaultLanguages = getLanguageList;
83
83
  /**
84
- * Serves files from the specified directory path in a locale-aware
84
+ * Serves files from the specified directory path or paths in a locale-aware
85
85
  * fashion after applying optional transforms.
86
86
  */
87
87
  const files = (params) => {
@@ -90,50 +90,55 @@ const files = (params) => {
90
90
  let exts = p.extensions ?? defaultExtensions;
91
91
  let fallthrough = p.fallthrough ?? true;
92
92
  return async (req, res, next) => {
93
- let langs = (p.languages ?? defaultLanguages)(req);
94
- let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
95
- if (!matches(path, p.matches)) {
93
+ let urlPath = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
94
+ if (!matches(urlPath, p.matches)) {
96
95
  if (fallthrough) next();
97
96
  else {
98
- emitLog(req.app, "Unmatched path", { data: { path } });
97
+ emitLog(req.app, "Unmatched path", { data: { urlPath } });
99
98
  res.status(404).send(await req.app.renderStatus?.(req, res, {
100
99
  code: "unmatched_path",
101
- path
100
+ urlPath
102
101
  }));
103
102
  }
104
103
  return;
105
104
  }
106
- if (path.includes("../")) {
105
+ if (urlPath.includes("../")) {
107
106
  if (fallthrough) next();
108
107
  else {
109
- emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
108
+ emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { urlPath } });
110
109
  res.status(400).send(await req.app.renderStatus?.(req, res, {
111
110
  code: "invalid_path",
112
- path
111
+ urlPath
113
112
  }));
114
113
  }
115
114
  return;
116
115
  }
116
+ let langs = (p.languages ?? defaultLanguages)(req);
117
117
  let filePath = null;
118
+ let urlExt = (0, node_path.extname)(urlPath);
118
119
  for (let k = 0; k < bases.length && filePath === null; k++) {
119
120
  let base = bases[k];
120
- if (!path.endsWith("/")) {
121
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${path}.${langs[i]}`);
122
- if (filePath === null) filePath = await resolve(base, path);
123
- 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]}`);
124
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, `${path}.${exts[i]}`);
121
+ if (!urlPath.endsWith("/")) {
122
+ for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve(base, `${urlPath}.${langs[i]}`);
123
+ if (filePath === null && urlExt) {
124
+ 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}`);
126
+ }
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]}`);
125
130
  }
126
- 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]}`);
127
- 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]}`);
128
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve(base, path, `index.${exts[i]}`);
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]}`);
129
134
  }
130
135
  if (filePath === null) {
131
136
  if (fallthrough) next();
132
137
  else {
133
- emitLog(req.app, "Unknown path", { data: { path } });
138
+ emitLog(req.app, "Unknown path", { data: { urlPath } });
134
139
  res.status(404).send(await req.app.renderStatus?.(req, res, {
135
140
  code: "unknown_path",
136
- path
141
+ urlPath
137
142
  }));
138
143
  }
139
144
  return;
@@ -143,17 +148,17 @@ const files = (params) => {
143
148
  return;
144
149
  }
145
150
  let content = (await (0, node_fs_promises.readFile)(filePath)).toString();
146
- let ext = (0, node_path.extname)(filePath);
147
- let name = (0, node_path.basename)(filePath, ext);
151
+ let fileExt = (0, node_path.extname)(filePath);
152
+ let fileName = (0, node_path.basename)(filePath, fileExt);
148
153
  for (let transform of p.transform) {
149
154
  let result = transform(req, res, {
150
155
  content,
151
156
  path: filePath,
152
- name
157
+ name: fileName
153
158
  });
154
159
  content = result instanceof Promise ? await result : result;
155
160
  }
156
- res.type(ext.slice(1)).send(content);
161
+ res.type(fileExt.slice(1)).send(content);
157
162
  };
158
163
  };
159
164
 
package/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ type FilesParams = {
27
27
  fallthrough?: boolean;
28
28
  };
29
29
  /**
30
- * Serves files from the specified directory path in a locale-aware
30
+ * Serves files from the specified directory path or paths in a locale-aware
31
31
  * fashion after applying optional transforms.
32
32
  */
33
33
  declare const files: Controller<string | FilesParams>;
package/dist/index.mjs CHANGED
@@ -55,7 +55,7 @@ const defaultExtensions = ["html", "htm"];
55
55
  const defaultPath = (req) => req.path;
56
56
  const defaultLanguages = getLanguageList;
57
57
  /**
58
- * Serves files from the specified directory path in a locale-aware
58
+ * Serves files from the specified directory path or paths in a locale-aware
59
59
  * fashion after applying optional transforms.
60
60
  */
61
61
  const files = (params) => {
@@ -64,50 +64,55 @@ const files = (params) => {
64
64
  let exts = p.extensions ?? defaultExtensions;
65
65
  let fallthrough = p.fallthrough ?? true;
66
66
  return async (req, res, next) => {
67
- let langs = (p.languages ?? defaultLanguages)(req);
68
- let path = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
69
- if (!matches(path, p.matches)) {
67
+ let urlPath = typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
68
+ if (!matches(urlPath, p.matches)) {
70
69
  if (fallthrough) next();
71
70
  else {
72
- emitLog(req.app, "Unmatched path", { data: { path } });
71
+ emitLog(req.app, "Unmatched path", { data: { urlPath } });
73
72
  res.status(404).send(await req.app.renderStatus?.(req, res, {
74
73
  code: "unmatched_path",
75
- path
74
+ urlPath
76
75
  }));
77
76
  }
78
77
  return;
79
78
  }
80
- if (path.includes("../")) {
79
+ if (urlPath.includes("../")) {
81
80
  if (fallthrough) next();
82
81
  else {
83
- emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { path } });
82
+ emitLog(req.app, "Invalid path (potential traversal attempt)", { data: { urlPath } });
84
83
  res.status(400).send(await req.app.renderStatus?.(req, res, {
85
84
  code: "invalid_path",
86
- path
85
+ urlPath
87
86
  }));
88
87
  }
89
88
  return;
90
89
  }
90
+ let langs = (p.languages ?? defaultLanguages)(req);
91
91
  let filePath = null;
92
+ let urlExt = extname(urlPath);
92
93
  for (let k = 0; k < bases.length && filePath === null; k++) {
93
94
  let base = bases[k];
94
- if (!path.endsWith("/")) {
95
- for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve$1(base, `${path}.${langs[i]}`);
96
- if (filePath === null) filePath = await resolve$1(base, path);
97
- 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, `${path}.${langs[i]}.${exts[j]}`);
98
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, `${path}.${exts[i]}`);
95
+ if (!urlPath.endsWith("/")) {
96
+ for (let i = 0; i < langs.length && filePath === null; i++) filePath = await resolve$1(base, `${urlPath}.${langs[i]}`);
97
+ if (filePath === null && urlExt) {
98
+ 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}`);
100
+ }
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]}`);
99
104
  }
100
- 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, `${path}.${langs[i]}`, `index.${exts[j]}`);
101
- 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, path, `index.${langs[i]}.${exts[j]}`);
102
- for (let i = 0; i < exts.length && filePath === null; i++) filePath = await resolve$1(base, path, `index.${exts[i]}`);
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]}`);
103
108
  }
104
109
  if (filePath === null) {
105
110
  if (fallthrough) next();
106
111
  else {
107
- emitLog(req.app, "Unknown path", { data: { path } });
112
+ emitLog(req.app, "Unknown path", { data: { urlPath } });
108
113
  res.status(404).send(await req.app.renderStatus?.(req, res, {
109
114
  code: "unknown_path",
110
- path
115
+ urlPath
111
116
  }));
112
117
  }
113
118
  return;
@@ -117,17 +122,17 @@ const files = (params) => {
117
122
  return;
118
123
  }
119
124
  let content = (await readFile(filePath)).toString();
120
- let ext = extname(filePath);
121
- let name = basename(filePath, ext);
125
+ let fileExt = extname(filePath);
126
+ let fileName = basename(filePath, fileExt);
122
127
  for (let transform of p.transform) {
123
128
  let result = transform(req, res, {
124
129
  content,
125
130
  path: filePath,
126
- name
131
+ name: fileName
127
132
  });
128
133
  content = result instanceof Promise ? await result : result;
129
134
  }
130
- res.type(ext.slice(1)).send(content);
135
+ res.type(fileExt.slice(1)).send(content);
131
136
  };
132
137
  };
133
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appstage",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -77,7 +77,7 @@ const defaultPath = (req: Request) => req.path;
77
77
  const defaultLanguages = getLanguageList;
78
78
 
79
79
  /**
80
- * Serves files from the specified directory path in a locale-aware
80
+ * Serves files from the specified directory path or paths in a locale-aware
81
81
  * fashion after applying optional transforms.
82
82
  */
83
83
  export const files: Controller<string | FilesParams> = (params) => {
@@ -88,20 +88,18 @@ export const files: Controller<string | FilesParams> = (params) => {
88
88
  let fallthrough = p.fallthrough ?? true;
89
89
 
90
90
  return async (req, res, next) => {
91
- let langs = (p.languages ?? defaultLanguages)(req);
92
-
93
- let path =
91
+ let urlPath =
94
92
  typeof p.path === "string" ? p.path : (p.path ?? defaultPath)(req);
95
93
 
96
- if (!matches(path, p.matches)) {
94
+ if (!matches(urlPath, p.matches)) {
97
95
  if (fallthrough) next();
98
96
  else {
99
- emitLog(req.app, "Unmatched path", { data: { path } });
97
+ emitLog(req.app, "Unmatched path", { data: { urlPath } });
100
98
 
101
99
  res.status(404).send(
102
100
  await req.app.renderStatus?.(req, res, {
103
101
  code: "unmatched_path",
104
- path,
102
+ urlPath,
105
103
  }),
106
104
  );
107
105
  }
@@ -109,17 +107,17 @@ export const files: Controller<string | FilesParams> = (params) => {
109
107
  return;
110
108
  }
111
109
 
112
- if (path.includes("../")) {
110
+ if (urlPath.includes("../")) {
113
111
  if (fallthrough) next();
114
112
  else {
115
113
  emitLog(req.app, "Invalid path (potential traversal attempt)", {
116
- data: { path },
114
+ data: { urlPath },
117
115
  });
118
116
 
119
117
  res.status(400).send(
120
118
  await req.app.renderStatus?.(req, res, {
121
119
  code: "invalid_path",
122
- path,
120
+ urlPath,
123
121
  }),
124
122
  );
125
123
  }
@@ -127,30 +125,42 @@ export const files: Controller<string | FilesParams> = (params) => {
127
125
  return;
128
126
  }
129
127
 
128
+ let langs = (p.languages ?? defaultLanguages)(req);
130
129
  let filePath: string | null = null;
130
+ let urlExt = extname(urlPath);
131
131
 
132
- // path: /x
133
- // langs: en, ru
132
+ // Example: path = /x, langs = [en, ru], exts = [html, htm]
134
133
  for (let k = 0; k < bases.length && filePath === null; k++) {
135
134
  let base = bases[k];
136
135
 
137
- if (!path.endsWith("/")) {
136
+ if (!urlPath.endsWith("/")) {
138
137
  // /x.en /x.ru
139
138
  for (let i = 0; i < langs.length && filePath === null; i++)
140
- filePath = await resolve(base, `${path}.${langs[i]}`);
139
+ filePath = await resolve(base, `${urlPath}.${langs[i]}`);
140
+
141
+ if (filePath === null && urlExt) {
142
+ let urlPathBase = urlPath.slice(0, -urlExt.length);
143
+
144
+ // /x.en.ext /x.ru.ext
145
+ for (let i = 0; i < langs.length && filePath === null; i++)
146
+ filePath = await resolve(
147
+ base,
148
+ `${urlPathBase}.${langs[i]}${urlExt}`,
149
+ );
150
+ }
141
151
 
142
152
  // /x
143
- if (filePath === null) filePath = await resolve(base, path);
153
+ if (filePath === null) filePath = await resolve(base, urlPath);
144
154
 
145
155
  // /x.en.html /x.en.htm /x.ru.html /x.ru.htm
146
156
  for (let i = 0; i < langs.length && filePath === null; i++) {
147
157
  for (let j = 0; j < exts.length && filePath === null; j++)
148
- filePath = await resolve(base, `${path}.${langs[i]}.${exts[j]}`);
158
+ filePath = await resolve(base, `${urlPath}.${langs[i]}.${exts[j]}`);
149
159
  }
150
160
 
151
161
  // /x.html /x.htm
152
162
  for (let i = 0; i < exts.length && filePath === null; i++)
153
- filePath = await resolve(base, `${path}.${exts[i]}`);
163
+ filePath = await resolve(base, `${urlPath}.${exts[i]}`);
154
164
  }
155
165
 
156
166
  // /x.en/index.html /x.en/index.htm /x.ru/index.html /x.ru/index.htm
@@ -158,7 +168,7 @@ export const files: Controller<string | FilesParams> = (params) => {
158
168
  for (let j = 0; j < exts.length && filePath === null; j++)
159
169
  filePath = await resolve(
160
170
  base,
161
- `${path}.${langs[i]}`,
171
+ `${urlPath}.${langs[i]}`,
162
172
  `index.${exts[j]}`,
163
173
  );
164
174
  }
@@ -166,23 +176,27 @@ export const files: Controller<string | FilesParams> = (params) => {
166
176
  // /x/index.en.html /x/index.en.htm /x/index.ru.html /x/index.ru.htm
167
177
  for (let i = 0; i < langs.length && filePath === null; i++) {
168
178
  for (let j = 0; j < exts.length && filePath === null; j++)
169
- filePath = await resolve(base, path, `index.${langs[i]}.${exts[j]}`);
179
+ filePath = await resolve(
180
+ base,
181
+ urlPath,
182
+ `index.${langs[i]}.${exts[j]}`,
183
+ );
170
184
  }
171
185
 
172
186
  // /x/index.html /x/index.htm
173
187
  for (let i = 0; i < exts.length && filePath === null; i++)
174
- filePath = await resolve(base, path, `index.${exts[i]}`);
188
+ filePath = await resolve(base, urlPath, `index.${exts[i]}`);
175
189
  }
176
190
 
177
191
  if (filePath === null) {
178
192
  if (fallthrough) next();
179
193
  else {
180
- emitLog(req.app, "Unknown path", { data: { path } });
194
+ emitLog(req.app, "Unknown path", { data: { urlPath } });
181
195
 
182
196
  res.status(404).send(
183
197
  await req.app.renderStatus?.(req, res, {
184
198
  code: "unknown_path",
185
- path,
199
+ urlPath,
186
200
  }),
187
201
  );
188
202
  }
@@ -196,15 +210,19 @@ export const files: Controller<string | FilesParams> = (params) => {
196
210
  }
197
211
 
198
212
  let content = (await readFile(filePath)).toString();
199
- let ext = extname(filePath);
200
- let name = basename(filePath, ext);
213
+ let fileExt = extname(filePath);
214
+ let fileName = basename(filePath, fileExt);
201
215
 
202
216
  for (let transform of p.transform) {
203
- let result = transform(req, res, { content, path: filePath, name });
217
+ let result = transform(req, res, {
218
+ content,
219
+ path: filePath,
220
+ name: fileName,
221
+ });
204
222
 
205
223
  content = result instanceof Promise ? await result : result;
206
224
  }
207
225
 
208
- res.type(ext.slice(1)).send(content);
226
+ res.type(fileExt.slice(1)).send(content);
209
227
  };
210
228
  };