fulmine.js 5.19.2 → 5.19.3

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/src/usage.js CHANGED
@@ -23,10 +23,9 @@ const acorn = require("acorn");
23
23
  // GET alone (and a GET that declares a body falls back to the full header copy).
24
24
  const kGetSafe = Symbol("fulmine.getSafe");
25
25
 
26
- // What a handler may do with `req` and still let the header copy be skipped: members whose
27
- // reads never reach a header. Anything else, computed access included, keeps the copy.
28
- // req.res and req.app are deliberately absent: the walk judges only the member directly on
29
- // the parameter, so anything that can reach another object could reach headers through it.
26
+ // What a handler may do with `req` and still let the header copy be skipped: members whose reads
27
+ // never reach a header. Anything else, computed access included, keeps the copy. req.res and
28
+ // req.app are absent because they could reach headers through another object.
30
29
  const REQ_OK = new Set(["query", "params", "body", "method", "path", "url", "baseUrl", "originalUrl", "route"]);
31
30
 
32
31
  // Reading any of these needs the query string fetched: req.url and req.originalUrl carry it
@@ -70,9 +69,8 @@ const QUERY = 8; // reads req.query, req.url or req.originalUrl
70
69
  const verdicts = new WeakMap();
71
70
 
72
71
  /**
73
- * What one callback provably does, as a mask of the facts above. The default is UNKNOWN: any
74
- * shape this walk does not understand and any alias of req, res or next could do anything.
75
- * That inversion is what makes source analysis sound to act on.
72
+ * What one callback provably does, as a mask of the facts above. The default is UNKNOWN: any shape
73
+ * this walk does not understand, and any alias of req, res or next, could do anything.
76
74
  *
77
75
  * @param {Function} fn
78
76
  * @returns {number}
@@ -135,9 +133,8 @@ function analyze(fn) {
135
133
  const resName = params[1] ? params[1].name : null;
136
134
  const nextName = params[2] ? params[2].name : null;
137
135
 
138
- // Every appearance of the three names in the whole body is judged, nested functions
139
- // included: an inner binding that shadows one of them only makes this stricter, never
140
- // looser, so scope tracking is not needed for soundness.
136
+ // Every appearance of the three names is judged, nested functions included. An inner binding
137
+ // that shadows one only makes this stricter, so no scope tracking is needed.
141
138
  let mask = 0;
142
139
  walk(root.body, null, (node, parent) => {
143
140
  if (mask & UNKNOWN) {
@@ -192,11 +189,10 @@ function analyze(fn) {
192
189
  return;
193
190
  }
194
191
  if (name === nextName) {
195
- // calling next is how a chain advances, and past its end or with an error the
196
- // request lands in the framework's own final answer, which the constructor's
197
- // accept pre-read covers. Anything but a direct call aliases the continuation,
198
- // and an argument that could be the string "route" would leave the chain for
199
- // routes nobody analyzed, so only shapes that cannot be a string pass.
192
+ // calling next is how a chain advances, and past its end the request lands in the
193
+ // framework's own final answer, which the constructor's accept pre-read covers.
194
+ // Anything but a direct call aliases the continuation, and an argument that could be
195
+ // the string "route" would leave the chain, so only non-string shapes pass.
200
196
  if (!parent || parent.type !== "CallExpression" || parent.callee !== node) {
201
197
  mask |= UNKNOWN;
202
198
  return;
@@ -235,8 +231,8 @@ function analyze(fn) {
235
231
  * Walks every node, handing each its parent. Arrays and nested objects are entered, nothing
236
232
  * is interpreted: the judging happens in the visitor.
237
233
  *
238
- * @param {any} node
239
- * @param {any} parent
234
+ * @param {any} node an acorn AST node. acorn ships no useful node types, and every shape here is checked by hand
235
+ * @param {any} parent its parent node, or null at the root
240
236
  * @param {(node: any, parent: any) => void} visit
241
237
  */
242
238
  function walk(node, parent, visit) {
@@ -265,10 +261,9 @@ function walk(node, parent, visit) {
265
261
  * What a native route's whole chain provably never does, so the request constructor may leave
266
262
  * that work undone: skipHeaders spares the header copy, skipQuery the query fetch.
267
263
  *
268
- * A callback that calls next() bare passes anywhere but in the terminal route, where it would
269
- * fall out of the chain: there it only passes when the caller established that no later route
270
- * could catch the fall-through. The framework's own 404 answers with the path alone, so the
271
- * fall-through itself needs neither headers nor query.
264
+ * A bare next() passes anywhere but in the terminal route, where it falls out of the chain: there
265
+ * it passes only when no later route could catch the fall-through. The framework's own 404 answers
266
+ * from the path alone.
272
267
  *
273
268
  * @param {any[]} chain the routes the native handler runs, in order, this route last
274
269
  * @param {boolean} allowTerminalNext whether a fall-through past the chain lands only in the
package/src/utils.js CHANGED
@@ -28,6 +28,9 @@ const ms = require("ms");
28
28
  const fs = require("fs");
29
29
  const { Stats } = require("fs");
30
30
 
31
+ /** @typedef {import("./request.js")} Request */
32
+ /** @typedef {import("./response.js")} Response */
33
+
31
34
  const EMPTY_REGEX = new RegExp(``);
32
35
 
33
36
  // what express hands qs for a query string. allowPrototypes keeps a key named "constructor" or
@@ -231,11 +234,9 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
231
234
  let lastWildcardEnd = -1;
232
235
  // What path-to-regexp calls the wildcard backtrack: the literal text written since the last
233
236
  // wildcard. Once a wildcard has eaten slashes, a later one in the same path is held to a single
234
- // segment, or the two would divide the path between them in more than one way and the regex
235
- // would have to backtrack to find out which. /*a/*b against /x/y/ is the case that shows it:
236
- // express refuses it under strict routing, and a second greedy wildcard accepts it.
237
- // the text written since the last capture of any kind, and the text written since the last
238
- // wildcard, which are the two path-to-regexp weighs
237
+ // segment, or the two would divide the path in more than one way and the regex would have to
238
+ // backtrack. /*a/*b against /x/y/ shows it: express refuses it under strict routing.
239
+ // Two counters: text since the last capture of any kind, and text since the last wildcard
239
240
  let backtrack = "";
240
241
  let wildcardBacktrack = "";
241
242
  let lastCaptureWasWildcard = false;
@@ -405,16 +406,13 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
405
406
  i++;
406
407
 
407
408
  // When a :parameter precedes this group, that parameter is the one that gives ground
408
- // while backtracking, so this one must not swallow the separator as well. Express
409
- // splits /a.b.c against /:file{.:ext} as file=a.b, ext=c, which only works if ext
410
- // cannot contain a dot. After static text there is nothing to give ground, so the
411
- // parameter takes everything: /file{.:ext} against /file.tar.gz gives ext=tar.gz.
412
- // The whole of it, not its first character: /:foo{abc:bar} against /123abcabc splits as
413
- // foo=123 and bar=abc on express, and reading the separator as "a" left bar unable to
414
- // match its own text, so the group never matched and foo took the segment whole. More
415
- // than one character cannot go in a class, so it is written as a lookahead, and either
416
- // way the parameter is still allowed to be exactly the separator, as path-to-regexp
417
- // writes it.
409
+ // while backtracking, so this one must not swallow the separator too. Express splits
410
+ // /a.b.c against /:file{.:ext} as file=a.b, ext=c, which only works if ext cannot
411
+ // contain a dot. After static text nothing gives ground, so the parameter takes
412
+ // everything: /file{.:ext} against /file.tar.gz gives ext=tar.gz.
413
+ // The whole separator, not its first character: /:foo{abc:bar} against /123abcabc
414
+ // splits as foo=123 and bar=abc on express, and reading it as "a" left bar unable to
415
+ // match its own text. More than one character cannot go in a class, so it is a lookahead
418
416
  const colon = groupContent.indexOf(":");
419
417
  const separator = lastTokenWasParam && colon > 0 ? groupContent.slice(0, colon) : "";
420
418
  const escapedSeparator = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -445,13 +443,11 @@ function patternToRegex(pattern, isPrefix = false, caseSensitive = true, strict
445
443
  }
446
444
  }
447
445
  if (lastWildcard && lastWildcardEnd === regexPattern.length) {
448
- // A wildcard immediately before the group. `(?<w>[^]+)(?:group)?` can never let
449
- // the group match, because the wildcard is greedy and the group may be empty, and
450
- // making the wildcard lazy is not the same thing either: it gives the trailing
451
- // slash away, and /*path{.:ext} against /a/b/ then loses the empty last segment.
452
- // path-to-regexp writes the two branches out instead, group first and the
453
- // wildcard greedy in both, so that is what goes here. The second branch captures
454
- // the same parameter under a name of its own, which is what uniqueGroupName is for.
446
+ // A wildcard immediately before the group. `(?<w>[^]+)(?:group)?` can never let the
447
+ // group match, because the wildcard is greedy and the group may be empty, and a
448
+ // lazy wildcard is not the same thing either: it gives the trailing slash away, and
449
+ // /*path{.:ext} against /a/b/ loses the empty last segment. path-to-regexp writes
450
+ // the two branches out instead, group first and the wildcard greedy in both
455
451
  const second = uniqueGroupName(lastWildcard.name);
456
452
  wildcardNames.push(second);
457
453
  const withWildcard = regexPattern.slice(lastWildcard.start);
@@ -619,13 +615,13 @@ const NOT_A_LITERAL = /[:*{}\\]/;
619
615
  *
620
616
  * The answer is structural: no position where two different literals meet, and, when neither path
621
617
  * can change length, the same number of segments. `/orders/:id` and `/invoices/:id` cannot both
622
- * match, `/users/:id` and `/users/me` can. The caller reads "do not know" as yes, so every doubt
623
- * answers true: saying two paths overlap only costs a native registration, while missing one lets
624
- * µWS answer a request that belonged to an earlier route.
618
+ * match, `/users/:id` and `/users/me` can. The caller reads "do not know" as yes: saying two paths
619
+ * overlap only costs a native registration, while missing one lets uWS answer a request that
620
+ * belonged to an earlier route.
625
621
  *
626
622
  * A parameter is not the only shape that matches more than itself. A wildcard and an optional group
627
- * do too, and reading `{:opt}` or `*splat` as the literal text it is written as reported "cannot
628
- * overlap" for a route that plainly could, which took the earlier route's turn away.
623
+ * do too, and reading `{:opt}` or `*splat` as literal text reported "cannot overlap" for a route
624
+ * that plainly could.
629
625
  *
630
626
  * @param {string} a
631
627
  * @param {string} b
@@ -759,11 +755,8 @@ function acceptParams(str) {
759
755
  //
760
756
  // The keys are media types, so an application uses a handful and the ceiling is never approached.
761
757
  // It is here because application code is free to hand res.type() something a client sent, and an
762
- // unbounded map keyed on that is a leak the client controls.
763
- //
764
- // Clearing beats evicting one entry at a time: the few types an application really uses are back
765
- // within a few requests, whereas refusing new entries once full would let a flood of invented
766
- // values lock the real ones out for the life of the process.
758
+ // unbounded map keyed on that is a leak the client controls. Clearing beats evicting one at a
759
+ // time: refusing new entries once full would let a flood of invented values lock the real ones out.
767
760
  const MEMO_LIMIT = 512;
768
761
 
769
762
  /**
@@ -818,7 +811,7 @@ function normalizeType(type) {
818
811
  * unicode escapes, so a string in the body cannot close a script tag in an HTML page that embeds
819
812
  * the response.
820
813
  *
821
- * @param {any} value
814
+ * @param {any} value whatever the handler passed to res.json
822
815
  * @param {any} [replacer] the "json replacer" setting
823
816
  * @param {string|number} [spaces] the "json spaces" setting
824
817
  * @param {boolean} [escape] the "json escape" setting
@@ -855,14 +848,12 @@ const ENCODING_ANY = ENCODING_BR | ENCODING_GZIP | ENCODING_DEFLATE | ENCODING_Z
855
848
  /**
856
849
  * The encoding to answer with, read straight off Accept-Encoding rather than through negotiator:
857
850
  * the header is a short list of names with an optional q, and building a Negotiator per response
858
- * to read it costs more than the scan does.
859
- *
860
- * The tie-break is negotiator's, for the list the compression module hands it: brotli first, then
861
- * gzip, then deflate, and identity last.
851
+ * costs more than the scan does. The tie-break is negotiator's, for the list the compression module
852
+ * hands it: brotli first, then gzip, then deflate, and identity last.
862
853
  *
863
- * Only the encodings named in `allowed` are on offer, since the caller may not be able to
864
- * produce all three: express.static offers the two it can have lying on disk. An uncompressed
865
- * answer is always on offer, and is what an empty header ends up choosing.
854
+ * Only the encodings named in `allowed` are on offer, since the caller may not produce all three:
855
+ * express.static offers the two it can have lying on disk. An uncompressed answer is always on
856
+ * offer, and is what an empty header chooses.
866
857
  *
867
858
  * @param {string} accept the header, or "" when the request carried none
868
859
  * @param {number} allowed ENCODING_BR, ENCODING_ZSTD, ENCODING_GZIP and ENCODING_DEFLATE, or'd
@@ -989,12 +980,10 @@ const defaultSettings = {
989
980
  // The native µWS router matches bytes, so the compiler in _compileOptimizedRoutes only hands
990
981
  // it routes whose earlier siblings it can prove agree under either case rule.
991
982
  "declarative responses": true,
992
- // on. Off hands every request to the ordinary chain instead of letting µWS match what it can,
983
+ // on. Off hands every request to the ordinary chain instead of letting uWS match what it can,
993
984
  // which is slower and answers the same. Not a tuning knob: it exists so one application can be
994
- // served both ways and the two sets of answers compared, which tests the optimizer against the
995
- // rest of the framework without a second framework to compare with. See
996
- // `npm run fuzz -- --self`. A compiled response needs a native registration to hang on, so this
997
- // takes "declarative responses" with it.
985
+ // served both ways and the answers compared, see `npm run fuzz -- --self`. A compiled response
986
+ // needs a native registration to hang on, so this takes "declarative responses" with it
998
987
  "native routes": true,
999
988
  // off: with a window set, the size and mtime of a file served by sendFile are remembered for
1000
989
  // it, which is one syscall less per request and a file that can be served as it was a moment
@@ -1053,7 +1042,7 @@ function cachedStat(file, ttl) {
1053
1042
  /**
1054
1043
  * A duration setting as milliseconds: false is off, a string is read by ms, a number is itself.
1055
1044
  *
1056
- * @param {any} value
1045
+ * @param {any} value the setting as the application wrote it
1057
1046
  * @param {string} name for the error, which names the setting the application wrote
1058
1047
  * @returns {number}
1059
1048
  */
@@ -1274,8 +1263,8 @@ function parseHttpDate(date) {
1274
1263
  * no longer the current one, which is a 412 rather than a 304: the client asked to be stopped if
1275
1264
  * anything had changed.
1276
1265
  *
1277
- * @param {any} req
1278
- * @param {any} res
1266
+ * @param {Request} req
1267
+ * @param {Response} res
1279
1268
  * @returns {boolean}
1280
1269
  */
1281
1270
  function isPreconditionFailure(req, res) {
@@ -1296,7 +1285,8 @@ function isPreconditionFailure(req, res) {
1296
1285
  // if-unmodified-since
1297
1286
  const unmodifiedSince = parseHttpDate(req.headers["if-unmodified-since"]);
1298
1287
  if (!isNaN(unmodifiedSince)) {
1299
- const lastModified = parseHttpDate(res.get("Last-Modified"));
1288
+ // cast because res.get answers an array for set-cookie, and never for this one
1289
+ const lastModified = parseHttpDate(/** @type {string|undefined} */ (res.get("Last-Modified")));
1300
1290
  return isNaN(lastModified) || lastModified > unmodifiedSince;
1301
1291
  }
1302
1292
 
@@ -1367,8 +1357,8 @@ function createETagGenerator(options) {
1367
1357
  * and sending the whole file. It may carry either an ETag or a date, and a date only counts when
1368
1358
  * it matches Last-Modified exactly.
1369
1359
  *
1370
- * @param {any} req
1371
- * @param {any} res
1360
+ * @param {Request} req
1361
+ * @param {Response} res
1372
1362
  * @returns {boolean}
1373
1363
  */
1374
1364
  function isRangeFresh(req, res) {
@@ -1384,7 +1374,8 @@ function isRangeFresh(req, res) {
1384
1374
  }
1385
1375
 
1386
1376
  // if-range as modified date
1387
- const lastModified = res.get("Last-Modified");
1377
+ // cast because res.get answers an array for set-cookie, and never for this one
1378
+ const lastModified = /** @type {string|undefined} */ (res.get("Last-Modified"));
1388
1379
  return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
1389
1380
  }
1390
1381
 
@@ -1489,10 +1480,9 @@ const HEADER_VALUE = /[^\t\x20-\x7e\x80-\xff]/;
1489
1480
  * One of node's header errors, built the way node builds it.
1490
1481
  *
1491
1482
  * Assigning the code is not the whole of it. Node also puts the code in the first line of the
1492
- * stack, by naming the error "TypeError [THE_CODE]" while V8 formats that line and then taking
1493
- * the name back off. Whatever prints a stack therefore says which code it was, and the default
1494
- * error page prints exactly that: without this, the same refusal reads "TypeError:" here and
1495
- * "TypeError [ERR_INVALID_CHAR]:" behind Express. Found by fuzzing against express.
1483
+ * stack, by naming the error "TypeError [THE_CODE]" while V8 formats that line and then taking the
1484
+ * name back off. The default error page prints exactly that: without this, the same refusal reads
1485
+ * "TypeError:" here and "TypeError [ERR_INVALID_CHAR]:" behind Express. Found by fuzzing.
1496
1486
  *
1497
1487
  * @param {string} message
1498
1488
  * @param {string} code
@@ -1515,7 +1505,7 @@ function headerError(message, code) {
1515
1505
  * Refuses a header name that is not an HTTP token, the way node's setHeader does and with its
1516
1506
  * error, so an application catching ERR_INVALID_HTTP_TOKEN behind Express catches it here.
1517
1507
  *
1518
- * @param {any} name
1508
+ * @param {any} name whatever a caller passed as a header name, which is what is being checked
1519
1509
  * @returns {void}
1520
1510
  * @throws {TypeError} if the name is not a token, which includes not being a string
1521
1511
  */
@@ -1567,7 +1557,7 @@ function validateHeaderValue(name, value) {
1567
1557
  * again is what turns one bad header into a dead process.
1568
1558
  *
1569
1559
  * @param {string} name
1570
- * @param {any} value
1560
+ * @param {any} value whatever a caller passed as a header value
1571
1561
  * @returns {boolean}
1572
1562
  */
1573
1563
  function headerIsWritable(name, value) {
@@ -1612,7 +1602,7 @@ function httpError(status) {
1612
1602
  *
1613
1603
  * The error itself is returned rather than a new one, so its errno, code, syscall and path survive.
1614
1604
  *
1615
- * @param {any} err
1605
+ * @param {any} err the fs error, which carries its errno and path
1616
1606
  * @returns {any} the same error
1617
1607
  */
1618
1608
  function asStatError(err) {
package/src/verify.js CHANGED
@@ -16,16 +16,12 @@ limitations under the License.
16
16
 
17
17
  // npx fulmine verify
18
18
  //
19
- // Whether this machine, and the image it will be deployed in, can run the thing at all. Not
20
- // whether the application behaves the same, which is what the test suite and `differences` are
21
- // for: this is the question that comes before it, and it is the one that costs an hour when the
22
- // answer is no and nobody asked.
19
+ // Whether this machine, and the image it will be deployed in, can run the thing at all. Not whether
20
+ // the application behaves the same, which is the test suite's job.
23
21
  //
24
- // There is a µWebSockets.js binary underneath, and a binary has requirements a package does not:
25
- // it is built per platform, per architecture and per node ABI, and it is linked against glibc. An
26
- // Alpine image, a node version the pinned build has no binary for, a musl base chosen by a
27
- // Dockerfile written before any of this: each one fails at require time, in a container, in CI,
28
- // with a message about a missing module that says nothing about what to do.
22
+ // There is a uWebSockets.js binary underneath, built per platform, per architecture and per node
23
+ // ABI, and linked against glibc. An Alpine image, a node version with no binary for it, a musl
24
+ // base: each one fails at require time with a message about a missing module.
29
25
  //
30
26
  // Thirty seconds here instead.
31
27
 
@@ -34,7 +30,7 @@ limitations under the License.
34
30
  const fs = require("fs");
35
31
  const path = require("path");
36
32
 
37
- // The oldest glibc the pinned µWS binaries are built against. A runtime older than this loads the
33
+ // The oldest glibc the pinned uWS binaries are built against. A runtime older than this loads the
38
34
  // file and then fails on a symbol, which is a worse error than not finding it at all.
39
35
  const MIN_GLIBC = "2.38";
40
36
 
@@ -44,8 +40,8 @@ const MIN_NODE = require("../package.json").engines.node.replace(/[^0-9.]/g, "")
44
40
  const MIN_NODE_MAJOR = MIN_NODE.split(".")[0];
45
41
  const SWAP_IMAGE = `node:${MIN_NODE_MAJOR}-trixie-slim`;
46
42
 
47
- // What a project may carry that needs a different API here rather than none. Everything that just
48
- // works, and everything that only wants a faster built-in, is `npx fulmine migrate`'s business.
43
+ // What a project may carry that needs a different API here. Everything that just works is
44
+ // `npx fulmine migrate`'s business.
49
45
  const NEEDS_A_LOOK = {
50
46
  "socket.io": "attach it with io.attachApp(app.uwsApp), not io.attach(server): there is no node socket to take over",
51
47
  ws: "the websocket server is µWS's own, through app.ws(path, behavior)",
@@ -55,9 +51,8 @@ const NEEDS_A_LOOK = {
55
51
  };
56
52
 
57
53
  /**
58
- * One line of the report. Three levels, and only one of them is a failure: an image that cannot
59
- * load the binary stops the deployment, while a dependency that wants a different call is
60
- * something to read, not something to fail a pipeline over.
54
+ * One line of the report. Only "no" is a failure: an image that cannot load the binary stops the
55
+ * deployment, a dependency that wants a different call does not.
61
56
  *
62
57
  * @param {"ok"|"note"|"no"} level
63
58
  * @param {string} what
@@ -92,9 +87,8 @@ function atLeast(version, minimum) {
92
87
  /**
93
88
  * The node this is running on, against what the package asks for.
94
89
  *
95
- * The version arrives as an argument rather than being read here, so the answer for a node this
96
- * machine is not running is testable from the machine it is not running on. Every check below
97
- * takes what it judges for the same reason.
90
+ * The version is an argument rather than read here, so a node this machine is not running is still
91
+ * testable. Every check below takes what it judges for the same reason.
98
92
  *
99
93
  * @param {string} [running] defaults to the node running this
100
94
  * @param {string} [required] defaults to what package.json asks for
@@ -121,9 +115,8 @@ function currentGlibc() {
121
115
  /**
122
116
  * Whether the C library is the one the binaries are linked against. Only linux has two of them.
123
117
  *
124
- * Both arguments are required, and deliberately: undefined is the answer that means musl, and a
125
- * default parameter fires on an explicit undefined, so a default here would quietly turn the musl
126
- * case into whatever this machine happens to run. Reading the machine is the caller's job.
118
+ * Both arguments are required on purpose: undefined means musl, and a default parameter fires on
119
+ * an explicit undefined, so a default would turn the musl case into whatever this machine runs.
127
120
  *
128
121
  * @param {string} platform
129
122
  * @param {string|undefined} glibc the runtime glibc, absent on musl
@@ -152,9 +145,8 @@ function checkLibc(platform, glibc) {
152
145
  }
153
146
 
154
147
  /**
155
- * Whether there is a µWebSockets.js binary for this platform, architecture and node ABI, which is
156
- * the failure that greets everyone who tries an unusual combination. The file is named rather than
157
- * loaded first, so the answer says which of the three does not line up.
148
+ * Whether there is a uWebSockets.js binary for this platform, architecture and node ABI. The file
149
+ * is named rather than loaded, so the answer says which of the three does not line up.
158
150
  *
159
151
  * @param {string} [platform]
160
152
  * @param {string} [arch]
@@ -218,8 +210,7 @@ function abiToNode(abi) {
218
210
  }
219
211
 
220
212
  /**
221
- * The base images a Dockerfile names, which is where the musl question is usually answered without
222
- * anybody meaning to.
213
+ * The base images a Dockerfile names, which is where the musl question is usually answered.
223
214
  *
224
215
  * @param {string} dir the project being verified
225
216
  * @returns {ReturnType<typeof result>[]}
@@ -309,8 +300,7 @@ function verify(argv) {
309
300
  console.log(` ${detail}`);
310
301
  }
311
302
  }
312
- // only a blocked start is a failure. A dependency that wants a different call is worth
313
- // reading and is not worth failing a pipeline over
303
+ // only a blocked start is a failure, a dependency that wants a different call is not
314
304
  const blocking = results.filter((entry) => entry.level === "no").length;
315
305
  const notes = results.filter((entry) => entry.level === "note").length;
316
306
  console.log(
package/src/view.js CHANGED
@@ -73,9 +73,8 @@ module.exports = class View {
73
73
  }
74
74
 
75
75
  /**
76
- * The first of the configured roots that actually holds this template, or undefined when none
77
- * of them does. `views` may be a single directory or a list, and the list is searched in order,
78
- * so an application can put its own templates in front of a package's.
76
+ * The first configured root that holds this template, or undefined. `views` may be one
77
+ * directory or a list, and the list is searched in order.
79
78
  *
80
79
  * @param {string} name template file name, relative to a root
81
80
  * @returns {string|undefined} absolute path to the file that exists
@@ -100,10 +99,9 @@ module.exports = class View {
100
99
  /**
101
100
  * Renders the template through its engine.
102
101
  *
103
- * The callback is always delivered asynchronously, even when the engine answers on the spot.
104
- * `sync` is still true only if the engine called back before this function returned, and in
105
- * that case the callback is pushed to the next tick, so a caller never has to handle both
106
- * orders. Express normalises it the same way.
102
+ * The callback is always delivered asynchronously, even when the engine answers on the spot:
103
+ * `sync` is true only if the engine called back before this returned, and then the callback
104
+ * goes to the next tick. Express does the same.
107
105
  *
108
106
  * @param {Record<string, any>} options locals and engine options, passed through untouched
109
107
  * @param {Function} callback called with whatever the engine passed, which is normally