gitea push

This commit is contained in:
2026-05-09 12:19:29 -06:00
parent 06113c95b8
commit 429461e985
1481 changed files with 74306 additions and 52475 deletions
+64 -17
View File
@@ -89,6 +89,8 @@ const _withResolvers =
/** @type {WeakMap<FileSystem, PathCacheFunctions>} */
const _pathCacheByFs = new WeakMap();
const HASH_ESCAPE_RE = /#/g;
/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
/**
@@ -450,6 +452,16 @@ class StackEntry {
/**
* Walk the linked list looking for an entry with the same request shape.
* Set-compatible: callers that used `stack.has(entry)` keep working.
*
* NOTE: kept monomorphic on purpose. An earlier draft accepted a string
* query too (so pre-5.21 plugins keeping their own `Set<string>` of
* seen entries could probe the live stack with the formatted form),
* but adding the second shape regressed `doResolve`'s heap profile by
* ~1 MiB / 200 resolves on stack-churn — V8 keeps a polymorphic
* call-site state for `parent.has(stackEntry)` once `has` has two
* argument shapes. Plugins that need string membership can reach for
* `[...stack].find(e => e.includes(formattedString))` via the
* `String`-method proxies on `StackEntry` instead.
* @param {StackEntry} query entry to look for
* @returns {boolean} whether the stack already contains an equivalent entry
*/
@@ -493,7 +505,15 @@ class StackEntry {
* `Set` that was populated in insertion order would iterate. Pre-seeded
* legacy `Set<string>` entries come first so error-message output stays
* ordered oldest-to-newest.
* @returns {IterableIterator<StackEntry | string>} iterator
*
* Yields each entry as its formatted `toString()` form. Plugins written
* against the pre-5.21 `Set<string>` shape — e.g.
* `[...resolveContext.stack].find(a => a.includes("module:"))` — keep
* working unchanged because each yielded value is a plain string with
* all of `String.prototype` available natively. Resolves that never
* iterate the stack pay nothing; iteration costs one `toString()`
* allocation per stack frame.
* @returns {IterableIterator<string>} iterator
*/
*[Symbol.iterator]() {
if (this.preSeeded !== undefined) {
@@ -507,12 +527,14 @@ class StackEntry {
entries.push(node);
node = node.parent;
}
for (let i = entries.length - 1; i >= 0; i--) yield entries[i];
for (let i = entries.length - 1; i >= 0; i--) yield entries[i].toString();
}
/**
* Human-readable form used in recursion error messages and logs.
* Matches the historical string format so existing log parsers stay valid.
* Human-readable form used in recursion error messages, logs, and the
* iterator above. Not memoized: caching would require an extra slot on
* every `StackEntry`, which costs heap even on resolves that never look
* at the formatted form.
* @returns {string} formatted entry
*/
toString() {
@@ -887,16 +909,27 @@ class Resolver {
* @param {ResolveRequest} result result
* @returns {void}
*/
const finishResolved = (result) =>
callback(
const finishResolved = (result) => {
const resultPath = result.path;
if (resultPath === false) return callback(null, false, result);
const escapedPath = resultPath.includes("#")
? resultPath.replace(HASH_ESCAPE_RE, "\0#")
: resultPath;
const resultQuery = result.query;
let escapedQuery;
if (resultQuery) {
escapedQuery = resultQuery.includes("#")
? resultQuery.replace(HASH_ESCAPE_RE, "\0#")
: resultQuery;
} else {
escapedQuery = "";
}
return callback(
null,
result.path === false
? false
: `${result.path.replace(/#/g, "\0#")}${
result.query ? result.query.replace(/#/g, "\0#") : ""
}${result.fragment || ""}`,
`${escapedPath}${escapedQuery}${result.fragment || ""}`,
result,
);
};
/**
* @param {string[]} log logs
@@ -1051,9 +1084,7 @@ class Resolver {
* @type {Error & { recursion?: boolean }}
*/
const recursionError = new Error(
`Recursion in resolving\nStack:\n ${[...stackEntry]
.map((entry) => entry.toString())
.join("\n ")}`,
`Recursion in resolving\nStack:\n ${[...stackEntry].join("\n ")}`,
);
recursionError.recursion = true;
if (resolveContext.log) {
@@ -1106,9 +1137,25 @@ class Resolver {
[part.request, part.query, part.fragment] = parsedIdentifier;
if (part.request.length > 0) {
part.internal = this.isPrivate(identifier);
part.module = this.isModule(part.request);
part.directory = this.isDirectory(part.request);
// `getType` looks at the prefix of its input and the prefix is
// identical between `identifier` and `part.request` in every
// non-`\0`-escape case (slicing off `?query` / `#fragment` doesn't
// touch the head). `parseIdentifier`'s common fast path returns
// the same `identifier` reference as `parsedIdentifier[0]`, so a
// pointer-equality check detects the case where we can compute
// `getType` once and use it for both `module` and `internal`. The
// `\0#…` escape path produces a fresh `part.request` and falls
// through to the second `getType(identifier)` call to preserve
// the original `internal` flag.
const requestType = getType(part.request);
part.module = requestType === PathType.Normal;
part.internal =
identifier === part.request
? requestType === PathType.Internal
: getType(identifier) === PathType.Internal;
// `isDirectory` is just `endsWith("/")` — inline so `parse()`
// doesn't pay for the extra method dispatch on every resolve.
part.directory = part.request.endsWith("/");
if (part.directory) {
part.request = part.request.slice(0, -1);
}