ultimate-express 1.0.0

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.
Files changed (200) hide show
  1. package/.github/workflows/test.yml +27 -0
  2. package/EXPRESS_LICENSE +26 -0
  3. package/README.md +269 -0
  4. package/package.json +76 -0
  5. package/src/application.js +295 -0
  6. package/src/index.js +23 -0
  7. package/src/middlewares.js +95 -0
  8. package/src/request.js +324 -0
  9. package/src/response.js +686 -0
  10. package/src/router.js +490 -0
  11. package/src/utils.js +285 -0
  12. package/src/view.js +103 -0
  13. package/src/workers/fs.js +14 -0
  14. package/tests/index.js +64 -0
  15. package/tests/parts/.test/index.html +11 -0
  16. package/tests/parts/big.jpg +0 -0
  17. package/tests/parts/index.art +12 -0
  18. package/tests/parts/index.dot +12 -0
  19. package/tests/parts/index.ejs +12 -0
  20. package/tests/parts/index.handlebars +2 -0
  21. package/tests/parts/index.html +12 -0
  22. package/tests/parts/index.mustache +12 -0
  23. package/tests/parts/index.pug +6 -0
  24. package/tests/parts/index.swig +12 -0
  25. package/tests/parts/layouts/main.handlebars +12 -0
  26. package/tests/preload.cjs +5 -0
  27. package/tests/singular.js +41 -0
  28. package/tests/tests/app/app-engine.js +8 -0
  29. package/tests/tests/app/app-on-mount.js +24 -0
  30. package/tests/tests/app/app-path.js +21 -0
  31. package/tests/tests/app/app-route.js +30 -0
  32. package/tests/tests/app/options.js +16 -0
  33. package/tests/tests/app/setting-inheritance.js +16 -0
  34. package/tests/tests/engines/art-template.js +33 -0
  35. package/tests/tests/engines/dot.js +28 -0
  36. package/tests/tests/engines/ejs.js +25 -0
  37. package/tests/tests/engines/handlebars.js +28 -0
  38. package/tests/tests/engines/mustache.js +28 -0
  39. package/tests/tests/engines/pug.js +25 -0
  40. package/tests/tests/engines/swig.js +28 -0
  41. package/tests/tests/errors/error-handling-middleware.js +22 -0
  42. package/tests/tests/errors/next-error-optimized.js +26 -0
  43. package/tests/tests/errors/next-error.js +26 -0
  44. package/tests/tests/errors/unexpected-error-handling.js +18 -0
  45. package/tests/tests/listen/listen-random.js +11 -0
  46. package/tests/tests/listen/listen-specific.js +17 -0
  47. package/tests/tests/middlewares/body-json.js +31 -0
  48. package/tests/tests/middlewares/body-raw-deflate.js +43 -0
  49. package/tests/tests/middlewares/body-raw-gzip.js +43 -0
  50. package/tests/tests/middlewares/body-raw.js +41 -0
  51. package/tests/tests/middlewares/body-text.js +27 -0
  52. package/tests/tests/middlewares/body-urlencoded.js +30 -0
  53. package/tests/tests/middlewares/cookie-parser-signed.js +31 -0
  54. package/tests/tests/middlewares/cookie-parser.js +28 -0
  55. package/tests/tests/middlewares/cookie-session.js +40 -0
  56. package/tests/tests/middlewares/cors.js +29 -0
  57. package/tests/tests/middlewares/errorhandler.js +26 -0
  58. package/tests/tests/middlewares/express-fileupload-temp.js +46 -0
  59. package/tests/tests/middlewares/express-fileupload.js +28 -0
  60. package/tests/tests/middlewares/express-rate-limit.js +33 -0
  61. package/tests/tests/middlewares/express-session.js +37 -0
  62. package/tests/tests/middlewares/express-static-options.js +72 -0
  63. package/tests/tests/middlewares/express-static.js +40 -0
  64. package/tests/tests/middlewares/method-override.js +33 -0
  65. package/tests/tests/middlewares/multer.js +43 -0
  66. package/tests/tests/middlewares/multiple-middlewares.js +37 -0
  67. package/tests/tests/middlewares/response-time.js +29 -0
  68. package/tests/tests/middlewares/serve-index.js +38 -0
  69. package/tests/tests/middlewares/serve-static.js +38 -0
  70. package/tests/tests/middlewares/vhost.js +50 -0
  71. package/tests/tests/params/array-param.js +30 -0
  72. package/tests/tests/params/nested-params.js +24 -0
  73. package/tests/tests/params/param-errors.js +56 -0
  74. package/tests/tests/params/param-function.js +49 -0
  75. package/tests/tests/params/param-next-route.js +48 -0
  76. package/tests/tests/params/param-optimized.js +38 -0
  77. package/tests/tests/params/param-use.js +39 -0
  78. package/tests/tests/params/param.js +68 -0
  79. package/tests/tests/params/params-regex.js +26 -0
  80. package/tests/tests/params/params-use.js +20 -0
  81. package/tests/tests/params/params.js +35 -0
  82. package/tests/tests/req/req-accepts-charsets.js +40 -0
  83. package/tests/tests/req/req-accepts-encodings.js +36 -0
  84. package/tests/tests/req/req-accepts-languages.js +41 -0
  85. package/tests/tests/req/req-accepts.js +41 -0
  86. package/tests/tests/req/req-app.js +17 -0
  87. package/tests/tests/req/req-baseurl.js +38 -0
  88. package/tests/tests/req/req-connection.js +19 -0
  89. package/tests/tests/req/req-fresh.js +59 -0
  90. package/tests/tests/req/req-get.js +78 -0
  91. package/tests/tests/req/req-headers-distinct.js +72 -0
  92. package/tests/tests/req/req-headers.js +72 -0
  93. package/tests/tests/req/req-host.js +45 -0
  94. package/tests/tests/req/req-hostname.js +45 -0
  95. package/tests/tests/req/req-ip.js +19 -0
  96. package/tests/tests/req/req-is.js +44 -0
  97. package/tests/tests/req/req-original-url.js +29 -0
  98. package/tests/tests/req/req-param.js +29 -0
  99. package/tests/tests/req/req-protocol.js +20 -0
  100. package/tests/tests/req/req-query.js +23 -0
  101. package/tests/tests/req/req-range.js +48 -0
  102. package/tests/tests/req/req-raw-headers.js +72 -0
  103. package/tests/tests/req/req-subdomains.js +48 -0
  104. package/tests/tests/req/req-url-nested.js +27 -0
  105. package/tests/tests/req/req-url-optimized-router.js +26 -0
  106. package/tests/tests/req/req-url-optimized.js +23 -0
  107. package/tests/tests/req/req-url.js +36 -0
  108. package/tests/tests/req/req-xhr.js +23 -0
  109. package/tests/tests/res/head-content-length.js +18 -0
  110. package/tests/tests/res/head.js +47 -0
  111. package/tests/tests/res/injecting.js +25 -0
  112. package/tests/tests/res/piping.js +23 -0
  113. package/tests/tests/res/res-app.js +17 -0
  114. package/tests/tests/res/res-append.js +24 -0
  115. package/tests/tests/res/res-attachment.js +19 -0
  116. package/tests/tests/res/res-clear-cookie.js +18 -0
  117. package/tests/tests/res/res-cookie.js +22 -0
  118. package/tests/tests/res/res-download.js +36 -0
  119. package/tests/tests/res/res-format.js +57 -0
  120. package/tests/tests/res/res-get.js +18 -0
  121. package/tests/tests/res/res-headers-sent.js +18 -0
  122. package/tests/tests/res/res-json.js +17 -0
  123. package/tests/tests/res/res-jsonp.js +25 -0
  124. package/tests/tests/res/res-links.js +21 -0
  125. package/tests/tests/res/res-location.js +34 -0
  126. package/tests/tests/res/res-redirect.js +46 -0
  127. package/tests/tests/res/res-remove-header.js +19 -0
  128. package/tests/tests/res/res-send-file.js +17 -0
  129. package/tests/tests/res/res-send-status.js +17 -0
  130. package/tests/tests/res/res-send.js +69 -0
  131. package/tests/tests/res/res-set.js +28 -0
  132. package/tests/tests/res/res-status.js +18 -0
  133. package/tests/tests/res/res-type.js +19 -0
  134. package/tests/tests/res/res-vary.js +19 -0
  135. package/tests/tests/res/res-write.js +29 -0
  136. package/tests/tests/routers/complex-routers.js +34 -0
  137. package/tests/tests/routers/empty-router.js +25 -0
  138. package/tests/tests/routers/lot-of-routes.js +38 -0
  139. package/tests/tests/routers/mergeparams.js +42 -0
  140. package/tests/tests/routers/nested-routers.js +52 -0
  141. package/tests/tests/routers/router-options.js +68 -0
  142. package/tests/tests/routers/routers.js +45 -0
  143. package/tests/tests/routers/simple-routers.js +35 -0
  144. package/tests/tests/routing/all.js +47 -0
  145. package/tests/tests/routing/array-arguments.js +35 -0
  146. package/tests/tests/routing/array-use.js +33 -0
  147. package/tests/tests/routing/async-use.js +25 -0
  148. package/tests/tests/routing/complex-routes.js +50 -0
  149. package/tests/tests/routing/lot-of-param-routes.js +26 -0
  150. package/tests/tests/routing/lot-of-routes.js +59 -0
  151. package/tests/tests/routing/next-existent-optimized-route.js +29 -0
  152. package/tests/tests/routing/next-existent-route.js +29 -0
  153. package/tests/tests/routing/next-nonexistent-optimized-route.js +19 -0
  154. package/tests/tests/routing/next-nonexistent-route.js +19 -0
  155. package/tests/tests/routing/next-special-cases.js +54 -0
  156. package/tests/tests/routing/next-unoptimized.js +39 -0
  157. package/tests/tests/routing/no-path-use.js +29 -0
  158. package/tests/tests/routing/non-string-routes.js +27 -0
  159. package/tests/tests/routing/req-multiple-mountpaths.js +34 -0
  160. package/tests/tests/routing/simple-routes.js +29 -0
  161. package/tests/tests/routing/simple-use.js +52 -0
  162. package/tests/tests/routing/some-middlewares.js +27 -0
  163. package/tests/tests/routing/special-characters.js +28 -0
  164. package/tests/tests/routing/star.js +31 -0
  165. package/tests/tests/routing/sub-apps.js +32 -0
  166. package/tests/tests/routing/trailing-slash.js +37 -0
  167. package/tests/tests/routing/weird-route-start.js +18 -0
  168. package/tests/tests/send-file/accept-ranges.js +26 -0
  169. package/tests/tests/send-file/callback.js +23 -0
  170. package/tests/tests/send-file/default-error-routing.js +21 -0
  171. package/tests/tests/send-file/dotfiles.js +24 -0
  172. package/tests/tests/send-file/etag.js +19 -0
  173. package/tests/tests/send-file/fs-threads.js +39 -0
  174. package/tests/tests/send-file/head.js +49 -0
  175. package/tests/tests/send-file/headers.js +23 -0
  176. package/tests/tests/send-file/if-match.js +31 -0
  177. package/tests/tests/send-file/if-range.js +37 -0
  178. package/tests/tests/send-file/if-unmodified-since.js +32 -0
  179. package/tests/tests/send-file/immutable.js +22 -0
  180. package/tests/tests/send-file/large-file.js +19 -0
  181. package/tests/tests/send-file/last-modified.js +21 -0
  182. package/tests/tests/send-file/max-age.js +21 -0
  183. package/tests/tests/send-file/path-traversal.js +35 -0
  184. package/tests/tests/send-file/range.js +57 -0
  185. package/tests/tests/send-file/simple.js +18 -0
  186. package/tests/tests/settings/case-sensitive-routing.js +48 -0
  187. package/tests/tests/settings/env-errors.js +38 -0
  188. package/tests/tests/settings/etag.js +36 -0
  189. package/tests/tests/settings/json-escape.js +45 -0
  190. package/tests/tests/settings/json-replacer.js +50 -0
  191. package/tests/tests/settings/json-spaces.js +44 -0
  192. package/tests/tests/settings/query-parser.js +45 -0
  193. package/tests/tests/settings/strict-routing.js +64 -0
  194. package/tests/tests/settings/subdomain-offset.js +38 -0
  195. package/tests/tests/settings/trust-proxy-host.js +58 -0
  196. package/tests/tests/settings/trust-proxy-ip.js +58 -0
  197. package/tests/tests/settings/trust-proxy-ips.js +58 -0
  198. package/tests/tests/settings/trust-proxy-protocol.js +46 -0
  199. package/tests/tests/settings/x-powered-by.js +32 -0
  200. package/tests/uws.js +14 -0
package/src/utils.js ADDED
@@ -0,0 +1,285 @@
1
+ const mime = require("mime-types");
2
+ const path = require("path");
3
+ const proxyaddr = require("proxy-addr");
4
+ const qs = require("qs");
5
+ const etag = require("etag");
6
+ const { Stats } = require("fs");
7
+
8
+ function removeDuplicateSlashes(path) {
9
+ return path.replace(/\/{2,}/g, '/');
10
+ }
11
+
12
+ function patternToRegex(pattern, isPrefix = false) {
13
+ if(pattern instanceof RegExp) {
14
+ return pattern;
15
+ }
16
+ if(isPrefix && pattern === '/') {
17
+ return new RegExp(``);
18
+ }
19
+
20
+ let regexPattern = pattern
21
+ .replace(/\./g, '\\.')
22
+ .replace(/\-/g, '\\-')
23
+ .replace(/\*/g, '.*') // Convert * to .*
24
+ .replace(/:(\w+)(\(.+?\))?/g, (match, param, regex) => {
25
+ return `(?<${param}>${regex ? regex + '($|\\/)' : '[^/]+'})`;
26
+ }); // Convert :param to capture group
27
+
28
+ return new RegExp(`^${regexPattern}${isPrefix ? '(?=$|\/)' : '$'}`);
29
+ }
30
+
31
+ function needsConversionToRegex(pattern) {
32
+ if(pattern instanceof RegExp) {
33
+ return false;
34
+ }
35
+ if(pattern === '/*') {
36
+ return false;
37
+ }
38
+
39
+ return pattern.includes('*') ||
40
+ pattern.includes('?') ||
41
+ pattern.includes('+') ||
42
+ pattern.includes('(') ||
43
+ pattern.includes(')') ||
44
+ pattern.includes(':') ||
45
+ pattern.includes('{') ||
46
+ pattern.includes('}') ||
47
+ pattern.includes('[') ||
48
+ pattern.includes(']');
49
+ }
50
+
51
+ function acceptParams(str) {
52
+ const parts = str.split(/ *; */);
53
+ const ret = { value: parts[0], quality: 1, params: {} }
54
+
55
+ for (let i = 1; i < parts.length; ++i) {
56
+ const pms = parts[i].split(/ *= */);
57
+ if ('q' === pms[0]) {
58
+ ret.quality = parseFloat(pms[1]);
59
+ } else {
60
+ ret.params[pms[0]] = pms[1];
61
+ }
62
+ }
63
+
64
+ return ret;
65
+ }
66
+
67
+ function normalizeType(type) {
68
+ return ~type.indexOf('/') ?
69
+ acceptParams(type) :
70
+ { value: (mime.lookup(type) || 'application/octet-stream'), params: {} };
71
+ }
72
+
73
+ function stringify(value, replacer, spaces, escape) {
74
+ let json = replacer || spaces
75
+ ? JSON.stringify(value, replacer, spaces)
76
+ : JSON.stringify(value);
77
+
78
+ if (escape && typeof json === 'string') {
79
+ json = json.replace(/[<>&]/g, function (c) {
80
+ switch (c.charCodeAt(0)) {
81
+ case 0x3c:
82
+ return '\\u003c'
83
+ case 0x3e:
84
+ return '\\u003e'
85
+ case 0x26:
86
+ return '\\u0026'
87
+ default:
88
+ return c
89
+ }
90
+ });
91
+ }
92
+
93
+ return json;
94
+ }
95
+
96
+ const defaultSettings = {
97
+ 'jsonp callback name': 'callback',
98
+ 'env': () => process.env.NODE_ENV ?? 'development',
99
+ 'etag': 'weak',
100
+ 'etag fn': () => createETagGenerator({ weak: true }),
101
+ 'query parser': 'extended',
102
+ 'query parser fn': () => qs.parse,
103
+ 'subdomain offset': 2,
104
+ 'trust proxy': false,
105
+ 'views': () => path.join(process.cwd(), 'views'),
106
+ 'view cache': () => process.env.NODE_ENV === 'production',
107
+ 'x-powered-by': true,
108
+ 'case sensitive routing': true
109
+ };
110
+
111
+ function compileTrust(val) {
112
+ if (typeof val === 'function') return val;
113
+
114
+ if (val === true) {
115
+ // Support plain true/false
116
+ return function(){ return true };
117
+ }
118
+
119
+ if (typeof val === 'number') {
120
+ // Support trusting hop count
121
+ return function(a, i){ return i < val };
122
+ }
123
+
124
+ if (typeof val === 'string') {
125
+ // Support comma-separated values
126
+ val = val.split(',')
127
+ .map(function (v) { return v.trim() })
128
+ }
129
+
130
+ return proxyaddr.compile(val || []);
131
+ }
132
+
133
+ const shownWarnings = new Set();
134
+ function deprecated(oldMethod, newMethod, full = false) {
135
+ const err = new Error();
136
+ const pos = full ? err.stack.split('\n').slice(1).join('\n') : err.stack.split('\n')[3].trim().split('(').slice(1).join('(').split(')').slice(0, -1).join(')');
137
+ if(shownWarnings.has(pos)) return;
138
+ shownWarnings.add(pos);
139
+ console.warn(`${new Date().toLocaleString('en-UK', {
140
+ weekday: 'short',
141
+ year: 'numeric',
142
+ month: 'short',
143
+ day: 'numeric',
144
+ hour: 'numeric',
145
+ minute: 'numeric',
146
+ second: 'numeric',
147
+ timeZone: 'GMT',
148
+ timeZoneName: 'short'
149
+ })} u-express deprecated ${oldMethod}: Use ${newMethod} instead at ${pos}`);
150
+ }
151
+
152
+ function findIndexStartingFrom(arr, fn, index = 0) {
153
+ for(let i = index; i < arr.length; i++) {
154
+ if(fn(arr[i], i, arr)) {
155
+ return i;
156
+ }
157
+ }
158
+ return -1;
159
+ };
160
+
161
+ function decode (path) {
162
+ try {
163
+ return decodeURIComponent(path)
164
+ } catch (err) {
165
+ return -1
166
+ }
167
+ }
168
+
169
+ const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
170
+
171
+ function containsDotFile(parts) {
172
+ for(let i = 0; i < parts.length; i++) {
173
+ const part = parts[i];
174
+ if(part.length > 1 && part[0] === '.') {
175
+ return true;
176
+ }
177
+ }
178
+
179
+ return false;
180
+ }
181
+
182
+ function parseTokenList(str) {
183
+ let end = 0;
184
+ const list = [];
185
+ let start = 0;
186
+
187
+ // gather tokens
188
+ for (let i = 0, len = str.length; i < len; i++) {
189
+ switch(str.charCodeAt(i)) {
190
+ case 0x20: /* */
191
+ if (start === end) {
192
+ start = end = i + 1;
193
+ }
194
+ break;
195
+ case 0x2c: /* , */
196
+ if (start !== end) {
197
+ list.push(str.substring(start, end));
198
+ }
199
+ start = end = i + 1;
200
+ break;
201
+ default:
202
+ end = i + 1;
203
+ break;
204
+ }
205
+ }
206
+
207
+ // final token
208
+ if (start !== end) {
209
+ list.push(str.substring(start, end));
210
+ }
211
+
212
+ return list;
213
+ }
214
+
215
+
216
+ function parseHttpDate(date) {
217
+ const timestamp = date && Date.parse(date);
218
+ return typeof timestamp === 'number' ? timestamp : NaN;
219
+ }
220
+
221
+ function isPreconditionFailure(req, res) {
222
+ const match = req.headers['if-match'];
223
+
224
+ // if-match
225
+ if(match) {
226
+ const etag = res.get('etag');
227
+ return !etag || (match !== '*' && parseTokenList(match).every(match => {
228
+ return match !== etag && match !== 'W/' + etag && 'W/' + match !== etag;
229
+ }));
230
+ }
231
+
232
+ // if-unmodified-since
233
+ const unmodifiedSince = parseHttpDate(req.headers['if-unmodified-since']);
234
+ if(!isNaN(unmodifiedSince)) {
235
+ const lastModified = parseHttpDate(res.get('Last-Modified'));
236
+ return isNaN(lastModified) || lastModified > unmodifiedSince;
237
+ }
238
+
239
+ return false;
240
+ }
241
+
242
+ function createETagGenerator(options) {
243
+ return function generateETag (body, encoding) {
244
+ const buf = !(body instanceof Stats) && !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
245
+ return etag(buf, options);
246
+ }
247
+ }
248
+
249
+ function isRangeFresh(req, res) {
250
+ const ifRange = req.headers['if-range'];
251
+ if(!ifRange) {
252
+ return true;
253
+ }
254
+
255
+ // if-range as etag
256
+ if(ifRange.indexOf('"') !== -1) {
257
+ const etag = res.get('etag');
258
+ return Boolean(etag && ifRange.indexOf(etag) !== -1);
259
+ }
260
+
261
+ // if-range as modified date
262
+ const lastModified = res.get('Last-Modified');
263
+ return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
264
+ }
265
+
266
+ module.exports = {
267
+ removeDuplicateSlashes,
268
+ patternToRegex,
269
+ needsConversionToRegex,
270
+ acceptParams,
271
+ normalizeType,
272
+ stringify,
273
+ defaultSettings,
274
+ compileTrust,
275
+ deprecated,
276
+ UP_PATH_REGEXP,
277
+ decode,
278
+ containsDotFile,
279
+ parseTokenList,
280
+ parseHttpDate,
281
+ isPreconditionFailure,
282
+ createETagGenerator,
283
+ isRangeFresh,
284
+ findIndexStartingFrom
285
+ };
package/src/view.js ADDED
@@ -0,0 +1,103 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ module.exports = class View {
5
+ constructor(name, options) {
6
+ this.name = name;
7
+ this.options = options ? Object.assign({}, options) : {};
8
+ this.defaultEngine = options.defaultEngine;
9
+ this.ext = path.extname(name);
10
+ this.root = options.root;
11
+
12
+ if (!this.ext && !this.defaultEngine) {
13
+ throw new Error('No default engine was specified and no extension was provided.');
14
+ }
15
+
16
+ let fileName = name;
17
+ if(!this.ext) {
18
+ this.ext = this.defaultEngine[0] !== '.'
19
+ ? '.' + this.defaultEngine
20
+ : this.defaultEngine;
21
+
22
+ fileName += this.ext;
23
+ }
24
+
25
+ if (!this.options.engines[this.ext]) {
26
+ const mod = this.ext.slice(1);
27
+
28
+ // default engine export
29
+ const fn = require(mod).__express;
30
+
31
+ if (typeof fn !== 'function') {
32
+ throw new Error('Module "' + mod + '" does not provide a view engine.')
33
+ }
34
+
35
+ this.options.engines[this.ext] = fn;
36
+ }
37
+
38
+ this.engine = this.options.engines[this.ext];
39
+ this.path = path.join(this.root, fileName);
40
+ }
41
+
42
+ lookup(name) {
43
+ let path;
44
+ let roots = [].concat(this.root);
45
+ for (let i = 0; i < roots.length && !path; i++) {
46
+ const root = roots[i];
47
+
48
+ // resolve the path
49
+ const loc = path.resolve(root, name);
50
+ const dir = path.dirname(loc);
51
+ const file = path.basename(loc);
52
+
53
+ // resolve the file
54
+ path = this.resolve(dir, file);
55
+ }
56
+
57
+ return path;
58
+ }
59
+
60
+ // ill be real idk what exactly this does but express implements it this way
61
+ render(options, callback) {
62
+ let sync = true;
63
+ this.engine(this.path, options, function onRender() {
64
+ if(!sync) {
65
+ return callback.apply(this, arguments);
66
+ }
67
+
68
+ return process.nextTick(() => {
69
+ return callback.apply(this, arguments);
70
+ });
71
+ });
72
+
73
+ sync = false;
74
+ }
75
+
76
+ resolve(dir, file) {
77
+ const ext = this.ext;
78
+
79
+ // <path>.<ext>
80
+ let path = path.join(dir, file);
81
+ let stat = tryStat(path);
82
+
83
+ if(stat && stat.isFile()) {
84
+ return path;
85
+ }
86
+
87
+ // <path>/index.<ext>
88
+ path = path.join(dir, path.basename(file, ext) + ext);
89
+ stat = tryStat(path);
90
+
91
+ if(stat && stat.isFile()) {
92
+ return path;
93
+ }
94
+ }
95
+ }
96
+
97
+ function tryStat(path) {
98
+ try {
99
+ return fs.statSync(path);
100
+ } catch (e) {
101
+ return undefined;
102
+ }
103
+ }
@@ -0,0 +1,14 @@
1
+ const fs = require("fs");
2
+ const { parentPort } = require("worker_threads");
3
+
4
+ parentPort.on('message', (message) => {
5
+ if(message.type === 'readFile') {
6
+ try {
7
+ const data = fs.readFileSync(message.path);
8
+ const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
9
+ parentPort.postMessage({ key: message.key, data: ab }, [ab]);
10
+ } catch(err) {
11
+ parentPort.postMessage({ key: message.key, err: String(err) });
12
+ }
13
+ }
14
+ });
package/tests/index.js ADDED
@@ -0,0 +1,64 @@
1
+ // npm run test - runs all tests
2
+ // npm run test routing - runs all tests in the routing category
3
+ // npm run test tests/tests/routing - runs all tests in the routing category
4
+ // npm run test tests/tests/listen/listen-random.js - runs the test at tests/tests/listen/listen-random.js
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const test = require("node:test");
9
+ const childProcess = require("node:child_process");
10
+ const assert = require("node:assert");
11
+
12
+ const testPath = path.join(__dirname, 'tests');
13
+
14
+ let testCategories = fs.readdirSync(testPath).sort((a, b) => parseInt(a) - parseInt(b));
15
+ const filterPath = process.argv[2];
16
+
17
+ if(filterPath) {
18
+ if(!filterPath.endsWith('.js')) {
19
+ testCategories = testCategories.filter(category => category.startsWith(path.basename(filterPath)));
20
+ } else {
21
+ testCategories = [path.dirname(filterPath).split(path.sep).pop()];
22
+ }
23
+ }
24
+
25
+ for (const testCategory of testCategories) {
26
+ test(testCategory, async () => {
27
+ let tests = fs.readdirSync(path.join(__dirname, 'tests', testCategory)).sort((a, b) => parseInt(a) - parseInt(b));
28
+ for (const testName of tests) {
29
+ if(filterPath && filterPath.endsWith('.js')) {
30
+ if(path.basename(testName) !== path.basename(filterPath)) {
31
+ continue;
32
+ }
33
+ }
34
+ let testPath = path.join(__dirname, 'tests', testCategory, testName);
35
+ let testCode = fs.readFileSync(testPath, 'utf8').replace(`const express = require("../../../src/index.js");`, 'const express = require("express");');
36
+ fs.writeFileSync(testPath, testCode);
37
+ let testDescription = testCode.split('\n')[0].slice(2).trim();
38
+ if(testDescription.endsWith('OFF')) {
39
+ return true;
40
+ }
41
+
42
+ await new Promise(resolve => {
43
+ test(testDescription, () => {
44
+ process.stdout.write(testDescription + '...');
45
+ try {
46
+ let expressOutput = childProcess.execSync(`node ${testPath}`).toString();
47
+
48
+ fs.writeFileSync(testPath, testCode.replace(`const express = require("express");`, `const express = require("../../../src/index.js");`));
49
+ let uExpressOutput = childProcess.execSync(`node ${testPath}`).toString();
50
+
51
+ assert.strictEqual(uExpressOutput, expressOutput);
52
+ console.log('\x1b[32mOK\x1b[0m');
53
+ } catch (error) {
54
+ console.log('\x1b[31mFAIL\x1b[0m');
55
+ throw error;
56
+ } finally {
57
+ fs.writeFileSync(testPath, testCode);
58
+ resolve();
59
+ }
60
+ });
61
+ });
62
+ }
63
+ });
64
+ }
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Document</title>
7
+ </head>
8
+ <body>
9
+ test
10
+ </body>
11
+ </html>
Binary file
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}</title>
7
+ </head>
8
+ <body>
9
+ <h1>{{ message }}</h1>
10
+ <span>{{ asdf }}</span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>[[= title ]]</title>
7
+ </head>
8
+ <body>
9
+ <h1>[[= message ]]</h1>
10
+ <span>[[= asdf ]]</span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= title %></title>
7
+ </head>
8
+ <body>
9
+ <h1><%= message %></h1>
10
+ <span><%= asdf %></span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,2 @@
1
+ <h1>{{{ message }}}</h1>
2
+ <span>{{{ asdf }}}</span>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>test</title>
7
+ </head>
8
+ <body>
9
+ <h1>test</h1>
10
+ <span>index test</span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}</title>
7
+ </head>
8
+ <body>
9
+ <h1>{{ message }}</h1>
10
+ <span>{{ asdf }}</span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,6 @@
1
+ html
2
+ head
3
+ title= title
4
+ body
5
+ h1= message
6
+ span= asdf
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}</title>
7
+ </head>
8
+ <body>
9
+ <h1>{{ message }}</h1>
10
+ <span>{{ asdf }}</span>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Example App</title>
6
+ </head>
7
+ <body>
8
+
9
+ {{{body}}}
10
+
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ let _processExit = process.exit;
2
+ process.exit = (...args) => {
3
+ console.log('process.exit', ...args);
4
+ // _processExit(...args);
5
+ };
@@ -0,0 +1,41 @@
1
+ const childProcess = require("child_process");
2
+ const fs = require("fs");
3
+ const exitHook = require("exit-hook");
4
+ const { exit } = require("process");
5
+
6
+ let args = process.argv.slice(2);
7
+
8
+ let u = args.some(arg => arg === '-u');
9
+ args = args.filter(arg => arg !== '-u');
10
+
11
+ let path = args[0];
12
+
13
+ if (!path) {
14
+ console.error('Usage: node singular.js [-u] <path>');
15
+ process.exit(1);
16
+ }
17
+
18
+ if (u) {
19
+ console.log('Running as µExpress');
20
+ let code = fs.readFileSync(path, 'utf8');
21
+ fs.writeFileSync(path, code.replace('const express = require("express");', 'const express = require("../../../src/index.js");'));
22
+ } else {
23
+ let code = fs.readFileSync(path, 'utf8');
24
+ fs.writeFileSync(path, code.replace(`const express = require("../../../src/index.js");`, `const express = require("express");`));
25
+ console.log('Running as normal Express');
26
+ }
27
+
28
+ let node = childProcess.spawn('node', ['-r', './tests/preload.cjs', path]);
29
+
30
+ node.stdout.on('data', data => {
31
+ console.log(data.toString());
32
+ });
33
+
34
+ node.stderr.on('data', data => {
35
+ console.error(data.toString());
36
+ });
37
+
38
+ exitHook(() => {
39
+ let code = fs.readFileSync(path, 'utf8');
40
+ fs.writeFileSync(path, code.replaceAll(`const express = require("../../../src/index.js");`, `const express = require("express");`));
41
+ });
@@ -0,0 +1,8 @@
1
+ // must support app.engine()
2
+
3
+ const express = require("express");
4
+
5
+ const app = express();
6
+ app.engine('asdf', function test() {});
7
+
8
+ console.log(app.engines);
@@ -0,0 +1,24 @@
1
+ // must emit 'mount' when using subapp
2
+
3
+ const express = require("express");
4
+
5
+ const app = express();
6
+ const student = express();
7
+ const teacher = express();
8
+
9
+ teacher.on('mount', (parent) => {
10
+ console.log('aaaaaaaaaaaaaaaaaaaaaaaaaa');
11
+ });
12
+
13
+ student.on('mount', (parent) => {
14
+ console.log('dewd');
15
+ });
16
+
17
+ app.use('/student', student);
18
+ app.use('/teacher', teacher);
19
+
20
+ app.listen(13333, (err) => {
21
+ console.log("Server is running on port 13333");
22
+
23
+ process.exit(0);
24
+ });