fulmine.js 5.19.8 → 5.20.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.
- package/README.md +115 -985
- package/package.json +16 -4
- package/src/adopt.js +116 -5
- package/src/application.js +8 -4
- package/src/cli.js +26 -3
- package/src/compression.js +1 -0
- package/src/create.js +248 -0
- package/src/declarative.js +15 -3
- package/src/hot-settings.js +7 -0
- package/src/index.js +23 -0
- package/src/middlewares.js +6 -2
- package/src/node-shim.js +34 -10
- package/src/optimizer.js +24 -3
- package/src/request-utils.js +3 -2
- package/src/request.js +8 -15
- package/src/response-utils.js +1 -1
- package/src/response.js +15 -1
- package/src/route.js +12 -6
- package/src/router-utils.js +11 -2
- package/src/router.js +27 -4
- package/src/testing.js +1 -1
- package/src/utils.js +28 -9
- package/src/verify.js +108 -7
- package/src/walk.js +8 -3
- package/src/websocket.js +1 -1
package/src/utils.js
CHANGED
|
@@ -208,6 +208,9 @@ function getPatternMeta(pattern) {
|
|
|
208
208
|
* A bare `*`, an unnamed parameter, an inline regex like :id(\\d+) and the `+`, `?`, `()` operators
|
|
209
209
|
* throw: a route that quietly stops matching is worse than one that fails at startup. The names it
|
|
210
210
|
* captures go in a WeakMap beside the regex, see PatternMeta.
|
|
211
|
+
*
|
|
212
|
+
* @param {string|RegExp} pattern
|
|
213
|
+
* @returns {RegExp}
|
|
211
214
|
*/
|
|
212
215
|
function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict = false) {
|
|
213
216
|
if (pattern instanceof RegExp) {
|
|
@@ -222,11 +225,12 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
|
|
|
222
225
|
let regexPattern = "";
|
|
223
226
|
let i = 0;
|
|
224
227
|
const len = pattern.length;
|
|
225
|
-
const wildcardNames = [];
|
|
228
|
+
const wildcardNames = /** @type {string[]} */ ([]);
|
|
226
229
|
// express takes /:a/:a, and two capture groups cannot share a name, so a repeat is compiled
|
|
227
230
|
// under a spelling of its own and mapped back when the parameters are read out. Reading them
|
|
228
231
|
// in order then leaves the last occurrence in place, which is the value express reports
|
|
229
|
-
const groupOutputName = new Map();
|
|
232
|
+
const groupOutputName = /** @type {Map<string, string>} */ (new Map());
|
|
233
|
+
/** @param {string} name */
|
|
230
234
|
const uniqueGroupName = (name) => {
|
|
231
235
|
if (!groupOutputName.has(name)) {
|
|
232
236
|
groupOutputName.set(name, name);
|
|
@@ -255,7 +259,11 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
|
|
|
255
259
|
let lastCaptureWasWildcard = false;
|
|
256
260
|
let wildcardInSegment = false;
|
|
257
261
|
let paramInSegment = false;
|
|
258
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* Records literal text as it is emitted, which is what the rules above are written against.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} text
|
|
266
|
+
*/
|
|
259
267
|
const literal = (text) => {
|
|
260
268
|
backtrack += text;
|
|
261
269
|
if (lastCaptureWasWildcard) {
|
|
@@ -750,7 +758,8 @@ function acceptParams(str) {
|
|
|
750
758
|
const length = str.length;
|
|
751
759
|
const colonIndex = str.indexOf(";");
|
|
752
760
|
let index = colonIndex === -1 ? length : colonIndex;
|
|
753
|
-
const
|
|
761
|
+
const params = /** @type {Record<string, string>} */ ({});
|
|
762
|
+
const ret = { value: str.slice(0, index).trim(), quality: 1, params };
|
|
754
763
|
|
|
755
764
|
while (index < length) {
|
|
756
765
|
const splitIndex = str.indexOf("=", index);
|
|
@@ -1098,13 +1107,20 @@ function durationSetting(value, name) {
|
|
|
1098
1107
|
return parsed;
|
|
1099
1108
|
}
|
|
1100
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* The predicate "trust proxy" compiles to: whether the address at hop i is trusted. The address is
|
|
1112
|
+
* undefined over a unix socket, see Request#parsedIp.
|
|
1113
|
+
*
|
|
1114
|
+
* @typedef {(addr: string|undefined, i: number) => boolean} TrustFn
|
|
1115
|
+
*/
|
|
1116
|
+
|
|
1101
1117
|
/**
|
|
1102
1118
|
* Turns whatever "trust proxy" was set to into the function proxy-addr wants: a predicate saying
|
|
1103
1119
|
* whether the address at hop i is trusted. true trusts everything, a number trusts that many hops,
|
|
1104
1120
|
* and a string or a list is read as addresses and subnet names.
|
|
1105
1121
|
*
|
|
1106
|
-
* @param {boolean|number|string|string[]|
|
|
1107
|
-
* @returns {
|
|
1122
|
+
* @param {boolean|number|string|string[]|TrustFn} val
|
|
1123
|
+
* @returns {TrustFn}
|
|
1108
1124
|
*/
|
|
1109
1125
|
function compileTrust(val) {
|
|
1110
1126
|
if (typeof val === "function") return val;
|
|
@@ -1118,8 +1134,9 @@ function compileTrust(val) {
|
|
|
1118
1134
|
|
|
1119
1135
|
if (typeof val === "number") {
|
|
1120
1136
|
// Support trusting hop count
|
|
1121
|
-
|
|
1122
|
-
|
|
1137
|
+
const hops = val;
|
|
1138
|
+
return function (/** @type {string|undefined} */ a, /** @type {number} */ i) {
|
|
1139
|
+
return i < hops;
|
|
1123
1140
|
};
|
|
1124
1141
|
}
|
|
1125
1142
|
|
|
@@ -1130,7 +1147,9 @@ function compileTrust(val) {
|
|
|
1130
1147
|
});
|
|
1131
1148
|
}
|
|
1132
1149
|
|
|
1133
|
-
|
|
1150
|
+
// proxy-addr answers false to an address it cannot parse, undefined included, though its
|
|
1151
|
+
// typing does not admit one
|
|
1152
|
+
return /** @type {TrustFn} */ (proxyaddr.compile(val || []));
|
|
1134
1153
|
}
|
|
1135
1154
|
|
|
1136
1155
|
const shownWarnings = new Set();
|
package/src/verify.js
CHANGED
|
@@ -29,6 +29,7 @@ limitations under the License.
|
|
|
29
29
|
|
|
30
30
|
const fs = require("fs");
|
|
31
31
|
const path = require("path");
|
|
32
|
+
const { detectManager, UWS_SPEC, UWS_OVERRIDE } = require("./adopt.js");
|
|
32
33
|
|
|
33
34
|
// The oldest glibc the pinned uWS binaries are built against. A runtime older than this loads the
|
|
34
35
|
// file and then fails on a symbol, which is a worse error than not finding it at all.
|
|
@@ -251,6 +252,20 @@ function checkDockerfiles(dir) {
|
|
|
251
252
|
return results;
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
/**
|
|
256
|
+
* The project's package.json, or nothing where there is none to read.
|
|
257
|
+
*
|
|
258
|
+
* @param {string} dir
|
|
259
|
+
* @returns {any}
|
|
260
|
+
*/
|
|
261
|
+
function readPackage(dir) {
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
264
|
+
} catch {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
254
269
|
/**
|
|
255
270
|
* The dependencies that need a different API here. Read from package.json rather than from
|
|
256
271
|
* node_modules, so a project is answered before it installs anything.
|
|
@@ -261,12 +276,8 @@ function checkDockerfiles(dir) {
|
|
|
261
276
|
function checkDependencies(dir) {
|
|
262
277
|
/** @type {ReturnType<typeof result>[]} */
|
|
263
278
|
const results = [];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
267
|
-
} catch {
|
|
268
|
-
return results;
|
|
269
|
-
}
|
|
279
|
+
const pkg = readPackage(dir);
|
|
280
|
+
if (!pkg) return results;
|
|
270
281
|
const installed = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
271
282
|
for (const name of Object.keys(NEEDS_A_LOOK)) {
|
|
272
283
|
if (installed[name]) {
|
|
@@ -276,6 +287,83 @@ function checkDependencies(dir) {
|
|
|
276
287
|
return results;
|
|
277
288
|
}
|
|
278
289
|
|
|
290
|
+
// pnpm refuses a git dependency of a dependency since this version, and µWebSockets.js is one
|
|
291
|
+
const PNPM_BLOCKS_GIT_SUBDEPS = [10, 26];
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Whether the package manager will install this at all. pnpm 10.26 and later refuse a dependency
|
|
295
|
+
* of a dependency that comes from git, which µWebSockets.js does, so `pnpm add fulmine.js` fails
|
|
296
|
+
* before anything runs. What lets it through is readable from the project: the two lines
|
|
297
|
+
* `npx fulmine.js pnpm` writes, the setting turned off, or an override taking µWebSockets.js from
|
|
298
|
+
* a registry. pnpm 11 reads its settings from pnpm-workspace.yaml only, so that is what is read.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} dir
|
|
301
|
+
* @param {any} pkg the parsed package.json
|
|
302
|
+
* @returns {ReturnType<typeof result>|undefined} nothing to say for npm and yarn
|
|
303
|
+
*/
|
|
304
|
+
function checkPackageManager(dir, pkg) {
|
|
305
|
+
const { manager, why } = detectManager(dir, pkg);
|
|
306
|
+
if (manager !== "pnpm") return undefined;
|
|
307
|
+
|
|
308
|
+
const declared = /^pnpm@(\d+)\.(\d+)/.exec(pkg.packageManager ?? "");
|
|
309
|
+
if (declared) {
|
|
310
|
+
const [major, minor] = [Number(declared[1]), Number(declared[2])];
|
|
311
|
+
const [blockMajor, blockMinor] = PNPM_BLOCKS_GIT_SUBDEPS;
|
|
312
|
+
if (major < blockMajor || (major === blockMajor && minor < blockMinor)) {
|
|
313
|
+
return result("ok", `pnpm ${declared[1]}.${declared[2]} installs a git dependency of a dependency`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let workspace = "";
|
|
318
|
+
try {
|
|
319
|
+
workspace = fs.readFileSync(path.join(dir, "pnpm-workspace.yaml"), "utf8");
|
|
320
|
+
} catch {
|
|
321
|
+
// no workspace file, so every setting is at its default
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// the recipe: the project owns the git dependency, and the copy this package asks for is dropped
|
|
325
|
+
const dropped = new RegExp(`^\\s*["']?${UWS_OVERRIDE.replace(/\./g, "\\.")}["']?:\\s*["']?-["']?\\s*$`, "m").test(
|
|
326
|
+
workspace
|
|
327
|
+
);
|
|
328
|
+
const own = pkg.dependencies?.["uWebSockets.js"];
|
|
329
|
+
if (dropped && typeof own === "string") {
|
|
330
|
+
if (own === UWS_SPEC) {
|
|
331
|
+
return result(
|
|
332
|
+
"ok",
|
|
333
|
+
"pnpm, with µWebSockets.js as the project's own dependency at the pin this package uses"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return result(
|
|
337
|
+
"note",
|
|
338
|
+
`pnpm, with µWebSockets.js as the project's own dependency at ${own}`,
|
|
339
|
+
`this package pins ${UWS_SPEC} and was tested against it. \`npx fulmine.js pnpm\` writes the pin.`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (dropped) {
|
|
343
|
+
return result(
|
|
344
|
+
"no",
|
|
345
|
+
"pnpm-workspace.yaml drops µWebSockets.js from this package, and the project does not declare it",
|
|
346
|
+
`nothing would install it. \`npx fulmine.js pnpm\` adds "uWebSockets.js": "${UWS_SPEC}" to dependencies.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (/^\s*blockExoticSubdeps:\s*false\s*$/m.test(workspace)) {
|
|
351
|
+
return result("ok", "pnpm, with blockExoticSubdeps off in pnpm-workspace.yaml");
|
|
352
|
+
}
|
|
353
|
+
const registry = /^\s*["']?uWebSockets\.js["']?:\s*["']?([~^]?\d[^"'\s]*)/m.exec(workspace);
|
|
354
|
+
if (registry) {
|
|
355
|
+
return result("ok", `pnpm, with µWebSockets.js overridden to ${registry[1]} from a registry`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return result(
|
|
359
|
+
"no",
|
|
360
|
+
`pnpm (${why}) will refuse to install this`,
|
|
361
|
+
"pnpm 10.26 and later block a git dependency of a dependency, and µWebSockets.js is one:\n" +
|
|
362
|
+
" pnpm add fulmine.js fails with ERR_PNPM_EXOTIC_SUBDEP. `npx fulmine.js pnpm` writes the two\n" +
|
|
363
|
+
" lines that let it through, see docs/deployment.md."
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
279
367
|
/**
|
|
280
368
|
* Runs every check and prints the report. Anything that would stop the application from starting
|
|
281
369
|
* is a failure and the command exits non-zero, so it can be a step in a pipeline.
|
|
@@ -292,6 +380,10 @@ function verify(argv) {
|
|
|
292
380
|
results.push(libc);
|
|
293
381
|
}
|
|
294
382
|
results.push(checkBinary(), ...checkDockerfiles(dir), ...checkDependencies(dir));
|
|
383
|
+
const manager = checkPackageManager(dir, readPackage(dir) ?? {});
|
|
384
|
+
if (manager) {
|
|
385
|
+
results.push(manager);
|
|
386
|
+
}
|
|
295
387
|
|
|
296
388
|
console.log(`\nWhether this machine and this project can run fulmine.js\n`);
|
|
297
389
|
const label = { ok: "ok ", note: "note", no: "NO " };
|
|
@@ -312,4 +404,13 @@ function verify(argv) {
|
|
|
312
404
|
return blocking === 0 ? 0 : 1;
|
|
313
405
|
}
|
|
314
406
|
|
|
315
|
-
module.exports = {
|
|
407
|
+
module.exports = {
|
|
408
|
+
verify,
|
|
409
|
+
checkNode,
|
|
410
|
+
checkLibc,
|
|
411
|
+
currentGlibc,
|
|
412
|
+
checkBinary,
|
|
413
|
+
checkDockerfiles,
|
|
414
|
+
checkDependencies,
|
|
415
|
+
checkPackageManager
|
|
416
|
+
};
|
package/src/walk.js
CHANGED
|
@@ -79,6 +79,7 @@ class Walk {
|
|
|
79
79
|
//
|
|
80
80
|
// Null here and bound on the first route with more than one callback, the only shape that
|
|
81
81
|
// reads it: a request that never meets one paid a bind for nothing
|
|
82
|
+
/** @type {((err?: unknown) => void)|null} */
|
|
82
83
|
this.leaveRoute = null;
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -103,6 +104,7 @@ class Walk {
|
|
|
103
104
|
* a chain of N middlewares costs one promise instead of N nested ones.
|
|
104
105
|
*
|
|
105
106
|
* @param {number} startIndex where to resume the scan
|
|
107
|
+
* @returns {void}
|
|
106
108
|
*/
|
|
107
109
|
dispatch(startIndex) {
|
|
108
110
|
const req = this.req;
|
|
@@ -226,7 +228,7 @@ class Walk {
|
|
|
226
228
|
.then((resumed) => this.runRoute(resumed))
|
|
227
229
|
// wrapped so the native pair keeps the walk as receiver; a promise's reject
|
|
228
230
|
// would not have cared
|
|
229
|
-
.catch((err) => this.reject(err));
|
|
231
|
+
.catch((/** @type {unknown} */ err) => this.reject(err));
|
|
230
232
|
return;
|
|
231
233
|
}
|
|
232
234
|
return this.runRoute(continueRoute);
|
|
@@ -291,6 +293,7 @@ class Walk {
|
|
|
291
293
|
* way in, and then the route's callbacks run one after another through next().
|
|
292
294
|
*
|
|
293
295
|
* @param {true|"route"} continueRoute what _preprocessRequest decided: true to run, "route" to skip
|
|
296
|
+
* @returns {void}
|
|
294
297
|
*/
|
|
295
298
|
runRoute(continueRoute) {
|
|
296
299
|
const req = this.req;
|
|
@@ -347,6 +350,7 @@ class Walk {
|
|
|
347
350
|
*
|
|
348
351
|
* @param {number} kind what the callback is, one of the CALLBACK_ constants
|
|
349
352
|
* @param {Function} callback
|
|
353
|
+
* @returns {void}
|
|
350
354
|
*/
|
|
351
355
|
errorHop(kind, callback) {
|
|
352
356
|
const req = this.req;
|
|
@@ -441,6 +445,7 @@ class Walk {
|
|
|
441
445
|
* leave the route; with anything else, remember it as the error and carry on.
|
|
442
446
|
*
|
|
443
447
|
* @param {unknown} thingamabob what next() was called with: nothing, "route", or an error
|
|
448
|
+
* @returns {void}
|
|
444
449
|
*/
|
|
445
450
|
step(thingamabob) {
|
|
446
451
|
const req = this.req;
|
|
@@ -492,7 +497,7 @@ class Walk {
|
|
|
492
497
|
}
|
|
493
498
|
callback
|
|
494
499
|
._routeRequest(req, res, 0)
|
|
495
|
-
.then((routed) => {
|
|
500
|
+
.then((/** @type {RouteEntry|false} */ routed) => {
|
|
496
501
|
// the child's params are scoped to it, and must not leak into the routes after
|
|
497
502
|
if (pushedParams) {
|
|
498
503
|
req._paramStack.pop();
|
|
@@ -531,7 +536,7 @@ class Walk {
|
|
|
531
536
|
// a rejection out of the nested walk, or a throw above, must reject this one
|
|
532
537
|
// instead of dying as an unhandled rejection; wrapped for the native pair's
|
|
533
538
|
// receiver
|
|
534
|
-
.catch((err) => this.reject(err));
|
|
539
|
+
.catch((/** @type {unknown} */ err) => this.reject(err));
|
|
535
540
|
} else {
|
|
536
541
|
// errors and error handlers live out of line: this is the cold path, and its size
|
|
537
542
|
// was pushing step past the inlining threshold
|
package/src/websocket.js
CHANGED
|
@@ -208,7 +208,7 @@ function makeUpgradeHandler(app, path, behavior) {
|
|
|
208
208
|
* @param {Application} app
|
|
209
209
|
*/
|
|
210
210
|
function registerWebSocketRoutes(app) {
|
|
211
|
-
const routes = [];
|
|
211
|
+
const routes = /** @type {WsRoute[]} */ ([]);
|
|
212
212
|
collectRoutes(app, "", routes, new Set());
|
|
213
213
|
for (const route of routes) {
|
|
214
214
|
const uwsBehavior = { ...route.behavior };
|