Index: frontend/node_modules/enhanced-resolve/LICENSE
===================================================================
--- frontend/node_modules/enhanced-resolve/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,20 @@
+Copyright JS Foundation and other contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Index: frontend/node_modules/enhanced-resolve/README.md
===================================================================
--- frontend/node_modules/enhanced-resolve/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,784 @@
+# enhanced-resolve
+
+[![npm][npm]][npm-url]
+[![Build Status][build-status]][build-status-url]
+[![codecov][codecov-badge]][codecov-url]
+[![Install Size][size]][size-url]
+[![GitHub Discussions][discussion]][discussion-url]
+
+Offers an async require.resolve function. It's highly configurable.
+
+## Features
+
+- plugin system
+- provide a custom filesystem
+- sync and async node.js filesystems included
+
+## Getting Started
+
+### Install
+
+```sh
+# npm
+npm install enhanced-resolve
+# or Yarn
+yarn add enhanced-resolve
+# or pnpm
+pnpm add enhanced-resolve
+```
+
+### Resolve
+
+There is a Node.js API which allows to resolve requests according to the Node.js resolving rules.
+Sync, async (callback) and promise APIs are offered. A `create` method allows to create a custom resolve function.
+
+```js
+const resolve = require("enhanced-resolve");
+
+resolve("/some/path/to/folder", "module/dir", (err, result) => {
+	result; // === "/some/path/node_modules/module/dir/index.js"
+});
+
+resolve.sync("/some/path/to/folder", "../../dir");
+// === "/some/path/dir/index.js"
+
+const result = await resolve.promise("/some/path/to/folder", "../../dir");
+// === "/some/path/dir/index.js"
+
+const myResolve = resolve.create({
+	// or resolve.create.sync / resolve.create.promise
+	extensions: [".ts", ".js"],
+	// see more options below
+});
+
+myResolve("/some/path/to/folder", "ts-module", (err, result) => {
+	result; // === "/some/node_modules/ts-module/index.ts"
+});
+```
+
+### Public API
+
+All of the following are exposed from `require("enhanced-resolve")`.
+
+#### `resolve(context?, path, request, resolveContext?, callback)`
+
+Async Node-style resolver using the built-in defaults (`conditionNames: ["node"]`, `extensions: [".js", ".json", ".node"]`). `context` is optional; when omitted, a built-in Node context is used.
+
+```js
+const resolve = require("enhanced-resolve");
+
+resolve(__dirname, "./utils", (err, result) => {
+	// result === "/abs/path/to/utils.js"
+});
+```
+
+#### `resolve.sync(context?, path, request, resolveContext?) => string | false`
+
+Synchronous variant. Throws on failure, returns `false` when the resolve yields no result.
+
+```js
+const file = resolve.sync(__dirname, "./utils");
+```
+
+#### `resolve.promise(context?, path, request, resolveContext?) => Promise<string | false>`
+
+Promise variant of `resolve`.
+
+```js
+const file = await resolve.promise(__dirname, "./utils");
+```
+
+#### `resolve.create(options) => ResolveFunctionAsync`
+
+Builds a customized async resolve function. Options are the same as for [`ResolverFactory.createResolver`](#resolver-options); `fileSystem` defaults to the built-in Node.js filesystem.
+
+```js
+const resolveTs = resolve.create({ extensions: [".ts", ".tsx", ".js"] });
+
+resolveTs(__dirname, "./component", (err, result) => {
+	// result === "/abs/path/to/component.tsx"
+});
+```
+
+#### `resolve.create.sync(options) => ResolveFunction`
+
+Sync variant of `resolve.create`.
+
+```js
+const resolveTsSync = resolve.create.sync({ extensions: [".ts", ".js"] });
+const file = resolveTsSync(__dirname, "./component");
+```
+
+#### `resolve.create.promise(options) => ResolveFunctionPromise`
+
+Promise variant of `resolve.create`.
+
+```js
+const resolveTsPromise = resolve.create.promise({ extensions: [".ts", ".js"] });
+const file = await resolveTsPromise(__dirname, "./component");
+```
+
+#### `ResolverFactory.createResolver(options) => Resolver`
+
+Lower-level factory. Returns a `Resolver` whose `resolve`, `resolveSync`, and `resolvePromise` methods accept `(context, path, request, resolveContext, [callback])`. Use this when you need a reusable resolver instance or access to its `hooks` (see the [Plugins](#plugins) section). `fileSystem` is required here — the high-level `resolve.create` defaults it for you.
+
+```js
+const fs = require("fs");
+const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
+
+const resolver = ResolverFactory.createResolver({
+	fileSystem: new CachedInputFileSystem(fs, 4000),
+	extensions: [".js", ".json"],
+});
+
+// callback
+resolver.resolve({}, __dirname, "./utils", {}, (err, file) => {
+	// ...
+});
+
+// sync (requires a sync fileSystem)
+const fileSync = resolver.resolveSync({}, __dirname, "./utils");
+
+// promise
+const filePromise = await resolver.resolvePromise({}, __dirname, "./utils", {});
+```
+
+#### `CachedInputFileSystem(fileSystem, duration)`
+
+Wraps any Node-compatible `fs` to add an in-memory cache for `stat`, `readdir`, `readFile`, `readJson`, and `readlink`. `duration` is the cache TTL in milliseconds (typically `4000`). Call `.purge()` to invalidate, or `.purge(path)` / `.purge([path, ...])` to invalidate specific entries — do this whenever you know files changed (e.g. from a watcher).
+
+```js
+const fs = require("fs");
+const { CachedInputFileSystem } = require("enhanced-resolve");
+
+const cachedFs = new CachedInputFileSystem(fs, 4000);
+// later, when files change:
+cachedFs.purge("/abs/path/to/changed-file.js");
+```
+
+#### Exported plugins & helpers
+
+For use with the `plugins` option or as standalone utilities:
+
+- `ResolverFactory` — see above.
+- `CachedInputFileSystem` — see above.
+- `CloneBasenamePlugin(source, target)` — joins the directory's basename onto the path. See [Built-in Plugins](#built-in-plugins).
+- `LogInfoPlugin(source)` — logs pipeline state at a hook; enable by passing a `log` function on the `resolveContext`.
+- `TsconfigPathsPlugin(options)` — applies `tsconfig.json` `paths` / `baseUrl` mappings; typically configured via the `tsconfig` resolver option instead.
+- `forEachBail(array, iterator, callback)` — bail-style async iterator used internally; useful when authoring plugins that try several candidates in order.
+
+```js
+const { LogInfoPlugin } = require("enhanced-resolve");
+
+const resolver = ResolverFactory.createResolver({
+	fileSystem: cachedFs,
+	extensions: [".js"],
+	plugins: [new LogInfoPlugin("described-resolve")],
+});
+
+resolver.resolve(
+	{},
+	__dirname,
+	"./utils",
+	{ log: (msg) => console.log(msg) },
+	() => {},
+);
+```
+
+### Creating a Resolver
+
+The easiest way to create a resolver is to use the `createResolver` function on `ResolveFactory`, along with one of the supplied File System implementations.
+
+```js
+const fs = require("fs");
+const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
+
+// create a resolver
+const myResolver = ResolverFactory.createResolver({
+	// Typical usage will consume the `fs` + `CachedInputFileSystem`, which wraps Node.js `fs` to add caching.
+	fileSystem: new CachedInputFileSystem(fs, 4000),
+	extensions: [".js", ".json"],
+	/* any other resolver options here. Options/defaults can be seen below */
+});
+
+// resolve a file with the new resolver
+const context = {};
+const lookupStartPath = "/Users/webpack/some/root/dir";
+const request = "./path/to-look-up.js";
+const resolveContext = {};
+
+// callback
+myResolver.resolve(
+	context,
+	lookupStartPath,
+	request,
+	resolveContext,
+	(err /* Error */, filepath /* string */) => {
+		// Do something with the path
+	},
+);
+
+// promise
+try {
+	const filepath = await myResolver.resolvePromise(
+		context,
+		lookupStartPath,
+		request,
+		resolveContext,
+	);
+	// Do something with the path
+} catch (err) {
+	// handle resolve failure
+}
+
+// sync (requires a sync fileSystem, e.g. the default Node.js one)
+const filepath = myResolver.resolveSync(context, lookupStartPath, request);
+```
+
+#### Resolver Options
+
+| Field                    | Default                     | Description                                                                                                                                                                                                                                                                                 |
+| ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| alias                    | []                          | A list of module alias configurations or an object which maps key to value                                                                                                                                                                                                                  |
+| aliasFields              | []                          | A list of alias fields in description files                                                                                                                                                                                                                                                 |
+| extensionAlias           | {}                          | An object which maps extension to extension aliases                                                                                                                                                                                                                                         |
+| extensionAliasForExports | false                       | Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned)                                                                                                                                                                    |
+| cachePredicate           | function() { return true }; | A function which decides whether a request should be cached or not. An object is passed to the function with `path` and `request` properties.                                                                                                                                               |
+| cacheWithContext         | true                        | If unsafe cache is enabled, includes `request.context` in the cache key                                                                                                                                                                                                                     |
+| conditionNames           | []                          | A list of exports field condition names                                                                                                                                                                                                                                                     |
+| descriptionFiles         | ["package.json"]            | A list of description files to read from                                                                                                                                                                                                                                                    |
+| enforceExtension         | false                       | Enforce that a extension from extensions must be used                                                                                                                                                                                                                                       |
+| exportsFields            | ["exports"]                 | A list of exports fields in description files                                                                                                                                                                                                                                               |
+| extensions               | [".js", ".json", ".node"]   | A list of extensions which should be tried for files                                                                                                                                                                                                                                        |
+| fallback                 | []                          | Same as `alias`, but only used if default resolving fails                                                                                                                                                                                                                                   |
+| fileSystem               |                             | The file system which should be used                                                                                                                                                                                                                                                        |
+| fullySpecified           | false                       | Request passed to resolve is already fully specified and extensions or main files are not resolved for it (they are still resolved for internal requests)                                                                                                                                   |
+| mainFields               | ["main"]                    | A list of main fields in description files                                                                                                                                                                                                                                                  |
+| mainFiles                | ["index"]                   | A list of main files in directories                                                                                                                                                                                                                                                         |
+| modules                  | ["node_modules"]            | A list of directories to resolve modules from, can be absolute path or folder name                                                                                                                                                                                                          |
+| plugins                  | []                          | A list of additional resolve plugins which should be applied                                                                                                                                                                                                                                |
+| resolver                 | undefined                   | A prepared Resolver to which the plugins are attached                                                                                                                                                                                                                                       |
+| resolveToContext         | false                       | Resolve to a context instead of a file                                                                                                                                                                                                                                                      |
+| preferRelative           | false                       | Prefer to resolve module requests as relative request and fallback to resolving as module                                                                                                                                                                                                   |
+| preferAbsolute           | false                       | Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots                                                                                                                                                                                            |
+| restrictions             | []                          | A list of resolve restrictions                                                                                                                                                                                                                                                              |
+| roots                    | []                          | A list of root paths                                                                                                                                                                                                                                                                        |
+| symlinks                 | true                        | Whether to resolve symlinks to their symlinked location                                                                                                                                                                                                                                     |
+| tsconfig                 | false                       | TypeScript config for paths mapping. Can be `false` (disabled), `true` (use default `tsconfig.json`), a string path to `tsconfig.json`, or an object with `configFile`, `references`, and `baseUrl` options. Supports JSONC format (comments and trailing commas) like TypeScript compiler. |
+| tsconfig.configFile      | tsconfig.json               | Path to the tsconfig.json file                                                                                                                                                                                                                                                              |
+| tsconfig.references      | []                          | Project references. `'auto'` to load from tsconfig, or an array of paths to referenced projects                                                                                                                                                                                             |
+| tsconfig.baseUrl         | undefined                   | Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file                                                                                                                                                                       |
+| unsafeCache              | false                       | Use this cache object to unsafely cache the successful requests                                                                                                                                                                                                                             |
+
+#### Option Examples
+
+Small snippets for the non-obvious options. All options are passed to `resolve.create({ ... })` or `ResolverFactory.createResolver({ ... })`.
+
+**`alias`** — rewrite matching requests to a target path, module, or to `false` to ignore them. Accepts an object or an array of entries (array form lets you specify ordering / `onlyModule`).
+
+```js
+const options = {
+	alias: {
+		"@": path.resolve(__dirname, "src"), // @/utils → src/utils
+		lodash$: "lodash-es", // exact "lodash", not "lodash/foo"
+		"ignored-module": false, // short-circuit to an empty module
+	},
+};
+```
+
+**`aliasFields`** — read alias maps from fields in `package.json`. The `browser` field is the common case:
+
+```js
+const options = { aliasFields: ["browser"] };
+```
+
+**`extensionAlias`** — maps one request extension to a list of candidate extensions. Useful for TypeScript ESM where imports are written with `.js` but the source is `.ts`. Applies both to direct requests (e.g. `./foo.js`) and to paths produced by the package.json `imports` field (e.g. `#foo` → `./foo.js` → `./foo.ts`). By default it does **not** apply to paths produced by the `exports` field (to stay aligned with Node.js, which does not substitute extensions on package-exported paths) — see `extensionAliasForExports` below to opt in:
+
+```js
+const options = {
+	extensionAlias: {
+		".js": [".ts", ".js"],
+		".mjs": [".mts", ".mjs"],
+	},
+};
+```
+
+**`extensionAliasForExports`** — when `true`, also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default to match Node.js. Turn it on if you want full alignment with TypeScript's resolver for packages that ship `.ts` sources alongside the compiled `.js` files they list in `exports` (e.g. monorepo source packages, or the `eslint-import-resolver-typescript` use case):
+
+```js
+const options = {
+	extensionAlias: { ".js": [".ts", ".js"] },
+	extensionAliasForExports: true,
+};
+```
+
+**`conditionNames` + `exportsFields`** — pick which conditions to match in the `exports` field of `package.json`:
+
+```js
+const options = {
+	conditionNames: ["import", "node", "default"],
+	exportsFields: ["exports"],
+};
+```
+
+**`extensions`** — extensions to try for extensionless requests, in order:
+
+```js
+const options = { extensions: [".ts", ".tsx", ".js", ".json"] };
+```
+
+**`fallback`** — same shape as `alias`, but only consulted when the primary resolve fails. Handy for polyfills:
+
+```js
+const options = {
+	fallback: {
+		crypto: require.resolve("crypto-browserify"),
+		stream: false,
+	},
+};
+```
+
+**`modules`** — where to look for bare-module requests. Entries can be folder names (searched hierarchically up the tree) or absolute paths (searched directly):
+
+```js
+const options = { modules: [path.resolve(__dirname, "src"), "node_modules"] };
+```
+
+**`mainFields` / `mainFiles`** — fields in `package.json` to try for a package entry point, and filenames to try inside a directory:
+
+```js
+const options = {
+	mainFields: ["browser", "module", "main"],
+	mainFiles: ["index"],
+};
+```
+
+**`roots` + `preferAbsolute`** — resolve server-relative URLs (starting with `/`) against one or more root directories. With `preferAbsolute: true`, absolute-path resolution is tried before the roots are consulted.
+
+```js
+const options = {
+	roots: [path.resolve(__dirname, "public")],
+	preferAbsolute: false,
+};
+```
+
+**`restrictions`** — reject results that don't satisfy at least one restriction. Accepts strings (path prefixes) or `RegExp`s:
+
+```js
+const options = {
+	restrictions: [path.resolve(__dirname, "src"), /\.(js|ts)$/],
+};
+```
+
+**`tsconfig`** — apply TypeScript `paths` / `baseUrl` mappings. Either pass `true` to load `./tsconfig.json`, a path string, or a configuration object:
+
+```js
+const options = {
+	tsconfig: {
+		configFile: path.resolve(__dirname, "tsconfig.json"),
+		references: "auto", // honor project references declared in tsconfig
+	},
+};
+```
+
+**`symlinks`** — resolve to the real path by following symlinks. Set to `false` to keep the symlinked path (common for monorepo / pnpm layouts where you want module identity tied to the workspace location):
+
+```js
+const options = { symlinks: false };
+```
+
+**`fullySpecified`** — require fully-specified requests (no extension inference, no `index` lookup) for non-internal requests. Matches Node.js ESM semantics:
+
+```js
+const options = { fullySpecified: true };
+```
+
+**`unsafeCache`** — pass an object to use as an in-memory cache of successful resolves. Set to `true` to let the resolver allocate its own:
+
+```js
+const options = {
+	unsafeCache: {}, // or true
+	cacheWithContext: false, // skip context in the cache key — faster, but only safe if context doesn't change the result
+};
+```
+
+To observe whether a request was served from the cache, wrap the cache object in a `Proxy`. `UnsafeCachePlugin` reads entries with `cache[id]` (cache lookup) and writes them with `cache[id] = result` (cache store), so trapping `get` and `set` is enough to distinguish hits from misses:
+
+```js
+const cache = {};
+const observedCache = new Proxy(cache, {
+	get(target, name, receiver) {
+		const hit = name in target;
+		console.log(hit ? `[cache hit]  ${name}` : `[cache miss] ${name}`);
+		return Reflect.get(target, name, receiver);
+	},
+	set(target, name, value, receiver) {
+		console.log(`[cache set]  ${name}`);
+		return Reflect.set(target, name, value, receiver);
+	},
+});
+
+const resolver = ResolverFactory.createResolver({
+	fileSystem: new CachedInputFileSystem(fs, 4000),
+	extensions: [".js", ".json"],
+	unsafeCache: observedCache,
+});
+```
+
+The `name` argument is the cache id — a `JSON.stringify`'d object containing `type`, `context`, `path`, `query`, `fragment`, and `request` — so you can parse it to report on specific resolves. Only successful resolves go through the cache; failures never touch it.
+
+**`fileSystem`** — any `fs`-compatible implementation. Usually `new CachedInputFileSystem(fs, 4000)`; can be a virtual filesystem (e.g. `memfs`) for testing:
+
+```js
+const options = { fileSystem: new CachedInputFileSystem(require("fs"), 4000) };
+```
+
+**`plugins`** — additional plugin instances appended to the pipeline. See [Plugins](#plugins):
+
+```js
+const options = {
+	plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
+};
+```
+
+## Plugins
+
+Similar to `webpack`, the core of `enhanced-resolve` functionality is implemented as individual plugins that are executed using [`tapable`](https://github.com/webpack/tapable).
+These plugins can extend the functionality of the library, adding other ways for files/contexts to be resolved.
+
+A plugin should be a `class` (or its ES5 equivalent) with an `apply` method. The `apply` method will receive a `resolver` instance, that can be used to hook in to the event system.
+
+Plugins are executed in a pipeline, and register which event they should be executed before/after. `source` is the name of the event that starts the pipeline, and `target` is what event this plugin should fire, which is what continues the execution of the pipeline. For a full view of how these plugin events form a chain, see `lib/ResolverFactory.js`, in the `//// pipeline ////` section.
+
+### Built-in Plugins
+
+`enhanced-resolve` ships with the following plugins. Most of them are wired up automatically by `ResolverFactory` based on the [resolver options](#resolver-options); the ones exported from the package entry (`TsconfigPathsPlugin`, `CloneBasenamePlugin`, `LogInfoPlugin`) are the ones you're most likely to use explicitly.
+
+| Plugin                                   | Purpose                                                                                                                              |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| `AliasPlugin`                            | Replaces a matching request with one or more alternative targets. Powers the `alias` and `fallback` options.                         |
+| `AliasFieldPlugin`                       | Applies aliasing based on a field in the description file (e.g. the `browser` field). Powers `aliasFields`.                          |
+| `AppendPlugin`                           | Appends a string (typically an extension) to the current path. Used for `extensions`.                                                |
+| `CloneBasenamePlugin`                    | Joins the current directory basename onto the path (e.g. `/foo/bar` → `/foo/bar/bar`). Useful for directory-named main-file schemes. |
+| `ConditionalPlugin`                      | Forwards the request only when it matches a given partial request shape.                                                             |
+| `DescriptionFilePlugin`                  | Finds and loads the nearest description file (e.g. `package.json`) so other plugins can read its fields. Powers `descriptionFiles`.  |
+| `DirectoryExistsPlugin`                  | Only continues the pipeline if the current path is an existing directory.                                                            |
+| `ExportsFieldPlugin`                     | Resolves requests through the `exports` field of a package's description file. Powers `exportsFields` and `conditionNames`.          |
+| `ExtensionAliasPlugin`                   | Maps one extension to a list of alternative extensions (e.g. `.js` → `.ts`, `.js`). Powers `extensionAlias`.                         |
+| `FileExistsPlugin`                       | Only continues the pipeline if the current path is an existing file, and records the file as a dependency.                           |
+| `ImportsFieldPlugin`                     | Resolves `#name` requests through the `imports` field of the enclosing package.                                                      |
+| `JoinRequestPlugin`                      | Joins the current path with the current request into a new path.                                                                     |
+| `JoinRequestPartPlugin`                  | Splits a module request into module name + inner request, joining the inner request onto the path.                                   |
+| `LogInfoPlugin`                          | Emits verbose log output at a given pipeline step. Handy for debugging resolves via `resolveContext.log`.                            |
+| `MainFieldPlugin`                        | Uses a field in the description file (e.g. `main`) to point to the entry file of a package. Powers `mainFields`.                     |
+| `ModulesInHierarchicalDirectoriesPlugin` | Searches for a module by walking up parent directories (the standard `node_modules` lookup). Powers `modules`.                       |
+| `ModulesInRootPlugin`                    | Searches for a module in a single absolute directory. Powers absolute-path entries in `modules`.                                     |
+| `NextPlugin`                             | Forwards the request from one hook to another without modification — glue between pipeline steps.                                    |
+| `ParsePlugin`                            | Parses a raw request string into its components (path, query, fragment, module flag, etc.).                                          |
+| `PnpPlugin`                              | Resolves module requests through a Yarn PnP API when one is available.                                                               |
+| `RestrictionsPlugin`                     | Rejects results that don't match a list of path restrictions (strings or regular expressions). Powers `restrictions`.                |
+| `ResultPlugin`                           | Terminal plugin that fires the `result` hook — signals a successful resolve.                                                         |
+| `RootsPlugin`                            | Resolves server-relative URL requests (starting with `/`) against one or more root directories. Powers `roots`.                      |
+| `SelfReferencePlugin`                    | Resolves a package self-reference (e.g. `my-pkg/foo` from within `my-pkg`).                                                          |
+| `SymlinkPlugin`                          | Real paths the resolved file by following symlinks. Can be disabled via the `symlinks` option.                                       |
+| `TryNextPlugin`                          | Forwards the request to the next hook with a log message. Useful for trying alternative resolutions.                                 |
+| `TsconfigPathsPlugin`                    | Rewrites requests using the `paths` and `baseUrl` from a `tsconfig.json`. Powers the `tsconfig` option.                              |
+| `UnsafeCachePlugin`                      | Caches successful resolves in an in-memory map to speed up repeated requests. Powers `unsafeCache`.                                  |
+| `UseFilePlugin`                          | Joins a fixed filename onto the current path (e.g. `index`). Powers `mainFiles`.                                                     |
+
+#### Plugin wiring and goals
+
+One-line goal and default wiring (`source → target`) for each plugin. `*` means the plugin is tapped on several hooks — the common ones are listed. Plugins without a fixed wiring are user-tapped.
+
+- **`AliasPlugin`** — Goal: redirect requests matching a configured key to an alternative target. `raw-resolve` → `internal-resolve` for `alias`; `file` → `internal-resolve` as a last-chance remap; `described-resolve` → `internal-resolve` for `fallback`.
+- **`AliasFieldPlugin`** — Goal: apply aliases declared in a description-file field like `browser`, so environment-specific substitutions happen without user config. `raw-resolve` / `file` → `internal-resolve`.
+- **`AppendPlugin`** — Goal: try appending a fixed string (usually an extension) to the current path. `raw-file` → `file`, one instance per entry in `extensions`.
+- **`CloneBasenamePlugin`** — Goal: join the directory's basename onto the path (e.g. `/foo/bar` → `/foo/bar/bar`) for directory-named-main layouts. User-wired via `plugins`.
+- **`ConditionalPlugin`** — Goal: gate a forward on a partial match of the request shape (e.g. `{ module: true }`), used to fan-out at branching hooks. Tapped on `after-normal-resolve`, `resolve-as-module`, `described-relative`, and `raw-file`.
+- **`DescriptionFilePlugin`** — Goal: locate and attach the nearest description file (usually `package.json`) so downstream plugins can read its fields. `parsed-resolve` → `described-resolve`, `relative` → `described-relative`, `undescribed-resolve-in-package` → `resolve-in-package`, `undescribed-existing-directory` → `existing-directory`, `undescribed-raw-file` → `raw-file`.
+- **`DirectoryExistsPlugin`** — Goal: only continue the pipeline if the current path exists as a directory. `resolve-as-module` → `undescribed-resolve-in-package`, `directory` → `undescribed-existing-directory`.
+- **`ExportsFieldPlugin`** — Goal: map a request through the `exports` field of a package's description file (with `conditionNames`). `resolve-in-package` → `relative`.
+- **`ExtensionAliasPlugin`** — Goal: rewrite a request's extension to a list of candidate extensions (e.g. `.js` → `.ts`, `.js`) for TypeScript ESM and similar. `raw-resolve` → `normal-resolve` for direct requests; also `imports-field-relative` → `relative` so extension substitution applies to `imports`-field targets.
+- **`FileExistsPlugin`** — Goal: confirm a candidate path exists as a file and record it as a file dependency. `final-file` → `existing-file`.
+- **`ImportsFieldPlugin`** — Goal: resolve `#name` requests through the `imports` field of the enclosing package. `internal` → `imports-field-relative` (relative target) or `imports-resolve` (bare target).
+- **`JoinRequestPlugin`** — Goal: join the current path with the current request into a single concrete path. `after-normal-resolve` → `relative` (three stage-offset copies for `preferRelative`, `preferAbsolute`, and default), `resolve-in-existing-directory` → `relative`.
+- **`JoinRequestPartPlugin`** — Goal: split a module request into module name + inner request, joining the inner part onto the path. `module` → `resolve-as-module`.
+- **`LogInfoPlugin`** — Goal: emit verbose log output at a chosen hook; enable by passing a `log` function on `resolveContext`. User-wired via `plugins`.
+- **`MainFieldPlugin`** — Goal: follow a description-file field (e.g. `main`, `module`, `browser`) to the entry file of a package. `existing-directory` → `resolve-in-existing-directory`, one instance per entry in `mainFields`.
+- **`ModulesInHierarchicalDirectoriesPlugin`** — Goal: search for a module by walking up parent directories (the standard `node_modules` lookup). `raw-module` → `module`; when PnP is enabled, `alternate-raw-module` → `module` too.
+- **`ModulesInRootPlugin`** — Goal: search for a module in a single absolute directory (powers absolute-path entries in `modules`). `raw-module` → `module`.
+- **`NextPlugin`** — Goal: glue — forward the current request unchanged from one hook to another. Used across the pipeline wherever two hooks should run sequentially.
+- **`ParsePlugin`** — Goal: split the raw request string into path / query / fragment / `module` / `directory` / `internal` flags. `resolve` → `parsed-resolve`; also wired on `internal-resolve` and `imports-resolve`.
+- **`PnpPlugin`** — Goal: resolve bare-module requests through Yarn's PnP API when available. `raw-module` → `undescribed-resolve-in-package` on hit, `alternate-raw-module` on miss.
+- **`RestrictionsPlugin`** — Goal: reject resolved paths that don't satisfy at least one string prefix or RegExp. Tapped on `resolved`.
+- **`ResultPlugin`** — Goal: terminal plugin — fires the `result` lifecycle hook and signals a successful resolve. Tapped on `resolved`.
+- **`RootsPlugin`** — Goal: resolve server-relative URL requests (starting with `/`) against one or more root directories. `after-normal-resolve` → `relative`.
+- **`SelfReferencePlugin`** — Goal: resolve a package self-reference (`my-pkg/foo` from inside `my-pkg`) via its own `exports`. `raw-module` → `resolve-as-module`.
+- **`SymlinkPlugin`** — Goal: real-path the resolved file by following symlinks; can be disabled via `symlinks: false`. `existing-file` → `existing-file` (runs via a stage offset on the same hook).
+- **`TryNextPlugin`** — Goal: forward the request to another hook with a log message, useful for trying an alternative candidate. `raw-file` → `file` (as the "no extension" attempt) and user-wired.
+- **`TsconfigPathsPlugin`** — Goal: rewrite requests using the `paths` and `baseUrl` from a `tsconfig.json` (including project references). Taps `described-resolve` internally and forwards to `internal-resolve`; exported for direct use as well.
+- **`UnsafeCachePlugin`** — Goal: cache successful resolves in an in-memory map for repeated requests. `described-resolve` → `raw-resolve` (only when `unsafeCache` is enabled).
+- **`UseFilePlugin`** — Goal: join a fixed filename (e.g. `index`) onto the current path to try as an entry file. `existing-directory` / `undescribed-existing-directory` → `undescribed-raw-file`, one instance per entry in `mainFiles`.
+
+### Hooks
+
+A resolver exposes two kinds of [`tapable`](https://github.com/webpack/tapable) hooks:
+
+- **Lifecycle hooks** on `resolver.hooks` — fired by the resolver itself around each `resolve` call. Use these to observe, not to transform the request.
+- **Pipeline hooks** — the named steps that plugins tap as `source` and forward to as `target`. Every pipeline hook is an `AsyncSeriesBailHook<[request, resolveContext], request | null>`: return `callback()` to pass on, `callback(err)` to fail, or `callback(null, request)` to short-circuit with a result. Obtain them with `resolver.ensureHook(name)` (creates if missing) or `resolver.getHook(name)` (throws if missing); names are kebab-case or camelCase and are interchangeable.
+
+#### Lifecycle hooks
+
+| Hook          | Type                  | Fires when                                                                                                                           |
+| ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| `resolveStep` | `SyncHook`            | Every time the resolver hands a request to a pipeline hook. Arguments: `(hook, request)`. Ideal for tracing.                         |
+| `noResolve`   | `SyncHook`            | When a top-level `resolve()` call can't produce a result. Arguments: `(request, error)`.                                             |
+| `resolve`     | `AsyncSeriesBailHook` | Entry point of the pipeline (also listed below). Tap this to intercept requests before parsing.                                      |
+| `result`      | `AsyncSeriesHook`     | After a successful resolve, with the final request. Fired by `ResultPlugin`. Tap to observe/record results without short-circuiting. |
+
+#### Pipeline hooks
+
+Listed roughly in the order the default pipeline visits them. Full wiring lives in `lib/ResolverFactory.js` under `//// pipeline ////`.
+
+| Hook                             | Role                                                                                                                                                                                      |
+| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `resolve`                        | Entry point. `ParsePlugin` parses the raw request (path, query, fragment, module flag) and forwards to `parsed-resolve`.                                                                  |
+| `internal-resolve`               | Re-entry point used by internal rewrites (e.g. after an `alias` fires). Same role as `resolve`, but `fullySpecified` is forced off.                                                       |
+| `imports-resolve`                | Re-entry point for the target of an `imports` field match; prevents recursive `#` resolution per the Node.js ESM spec.                                                                    |
+| `parsed-resolve`                 | Request has been parsed. `DescriptionFilePlugin` attaches the nearest `package.json`, then forwards to `described-resolve`.                                                               |
+| `described-resolve`              | Description file is attached. Where `unsafeCache`, `fallback`, and most user plugins (including `MyLibSrcPlugin` below) hook in.                                                          |
+| `raw-resolve`                    | After description. Where `alias`, `aliasFields`, `tsconfig` paths, and `extensionAlias` rewrites fire before default resolution.                                                          |
+| `normal-resolve`                 | Default resolution starts. Branches into `relative` (for `./`, `../`, absolute), `raw-module` (bare modules), or `internal` (`#imports`).                                                 |
+| `internal`                       | `#name` imports-field entry. `ImportsFieldPlugin` maps the specifier and forwards to `imports-field-relative` or `imports-resolve`.                                                       |
+| `imports-field-relative`         | Concrete path from an `imports`-field match, before the normal `relative` flow. `ExtensionAliasPlugin` taps here so `.js` → `.ts` also fires for `#name` targets. Forwards to `relative`. |
+| `raw-module`                     | Bare-module lookup. `SelfReferencePlugin`, `ModulesInHierarchicalDirectoriesPlugin`, `ModulesInRootPlugin`, and `PnpPlugin` all tap here.                                                 |
+| `alternate-raw-module`           | Fallback module lookup used by `PnpPlugin` when PnP can't resolve and `node_modules` should be tried.                                                                                     |
+| `module`                         | A candidate module directory was built. `JoinRequestPartPlugin` splits off the inner request and forwards to `resolve-as-module`.                                                         |
+| `resolve-as-module`              | Treat candidate as a package. `DirectoryExistsPlugin` gates on existence; short single-file modules may re-enter via `undescribed-raw-file`.                                              |
+| `undescribed-resolve-in-package` | Inside a located package directory, before its `package.json` has been read. Loads the description, forwards to `resolve-in-package`.                                                     |
+| `resolve-in-package`             | Inside a package with its description loaded. `ExportsFieldPlugin` matches `exports`, otherwise forwards to `resolve-in-existing-directory`.                                              |
+| `resolve-in-existing-directory`  | Package directory confirmed; join the remaining request onto it and continue at `relative`.                                                                                               |
+| `relative`                       | A concrete path on disk. `DescriptionFilePlugin` loads the nearest `package.json` and forwards to `described-relative`.                                                                   |
+| `described-relative`             | Branches to `raw-file` (treat as file) and `directory` (treat as directory). `resolveToContext` skips the file branch.                                                                    |
+| `directory`                      | Candidate directory. `DirectoryExistsPlugin` gates on existence and forwards to `undescribed-existing-directory`.                                                                         |
+| `undescribed-existing-directory` | Existing directory, before its `package.json` has been read. `UseFilePlugin` tries `mainFiles` via `undescribed-raw-file`.                                                                |
+| `existing-directory`             | Existing directory with description loaded. `MainFieldPlugin` tries `mainFields`; `UseFilePlugin` falls back to `mainFiles`.                                                              |
+| `undescribed-raw-file`           | Candidate file path, before description is read. Loads description, then forwards to `raw-file`.                                                                                          |
+| `raw-file`                       | Apply extension handling: `ConditionalPlugin` short-circuits when `fullySpecified`, `TryNextPlugin` + `AppendPlugin` try each extension.                                                  |
+| `file`                           | A specific file path. Last place `alias` and `aliasFields` can redirect; forwards to `final-file`.                                                                                        |
+| `final-file`                     | `FileExistsPlugin` checks the file is real and records it as a file dependency, then forwards to `existing-file`.                                                                         |
+| `existing-file`                  | Real file on disk. `SymlinkPlugin` real-paths symlinks (unless `symlinks: false`), then forwards to `resolved`.                                                                           |
+| `resolved`                       | Terminal hook. `RestrictionsPlugin` enforces `restrictions`; `ResultPlugin` fires the `result` lifecycle hook.                                                                            |
+
+#### `before-` and `after-` prefixes
+
+`ensureHook("before-foo")` and `getHook("before-foo")` return the `foo` hook with `stage: -10`; `after-foo` returns it with `stage: 10`. Use this to tap earlier or later than the default stage without creating a separate hook. You'll see `after-parsed-resolve`, `after-normal-resolve`, `after-relative`, and `after-undescribed-resolve-in-package` used this way inside `ResolverFactory`.
+
+#### Request flow by request type
+
+The same 26 pipeline hooks serve every request, but different request shapes take different paths through them. Each `➝` below is one `doResolve` / `NextPlugin` / plugin forward; `resolveStep` fires on every arrow, so tapping it (see [Hook examples](#hook-examples)) prints these chains live.
+
+Relative path (`./utils` from `/src/index.js`) — the default "resolve on disk" path:
+
+```text
+resolve                                    (ParsePlugin)
+  ➝ parsed-resolve                         (DescriptionFilePlugin attaches nearest package.json)
+  ➝ described-resolve                      (NextPlugin; or UnsafeCachePlugin short-circuit)
+  ➝ raw-resolve                            (NextPlugin; alias/tsconfig would branch here)
+  ➝ normal-resolve                         (JoinRequestPlugin: path=/src/utils, request="")
+  ➝ relative                               (DescriptionFilePlugin loads /src/package.json)
+  ➝ described-relative                     (branches to file and directory candidates)
+        ├─ ➝ raw-file                      (ConditionalPlugin / TryNextPlugin)
+        │     ➝ file                       (AppendPlugin tried each extension, e.g. .js)
+        │     ➝ final-file                 (FileExistsPlugin confirms the file)
+        │     ➝ existing-file              (SymlinkPlugin real-paths it)
+        │     ➝ resolved                   (RestrictionsPlugin → ResultPlugin)
+        └─ ➝ directory                     (DirectoryExistsPlugin; used when path is a dir)
+              ➝ undescribed-existing-directory
+              ➝ existing-directory         (MainFieldPlugin tries "main", UseFilePlugin tries "index")
+              ➝ undescribed-raw-file ➝ raw-file ➝ …
+```
+
+Bare module (`lodash/merge`) — walks up `node_modules`, then treats the hit as a package:
+
+```text
+resolve ➝ parsed-resolve ➝ described-resolve ➝ raw-resolve ➝ normal-resolve
+  ➝ raw-module                             (ConditionalPlugin {module:true})
+  ➝ module                                 (ModulesInHierarchicalDirectoriesPlugin walks
+                                            /src/node_modules, /node_modules, …)
+  ➝ resolve-as-module                      (JoinRequestPartPlugin splits "lodash" / "./merge")
+  ➝ undescribed-resolve-in-package         (DirectoryExistsPlugin gates on lodash/ existing)
+  ➝ resolve-in-package                     (DescriptionFilePlugin loads lodash/package.json)
+        ├─ ➝ relative                      (ExportsFieldPlugin, if "exports" matches)
+        └─ ➝ resolve-in-existing-directory (otherwise; JoinRequestPlugin joins "./merge")
+              ➝ relative ➝ … (same tail as the relative flow above)
+```
+
+Internal import (`#util` from inside a package) — re-enters the pipeline after mapping:
+
+```text
+resolve ➝ parsed-resolve ➝ described-resolve ➝ raw-resolve ➝ normal-resolve
+  ➝ internal                               (ConditionalPlugin {internal:true})
+  ➝ imports-resolve                        (ImportsFieldPlugin mapped "#util" to a bare target)
+  ➝ parsed-resolve ➝ …                     (fresh pipeline run, internal:false so # isn't remapped)
+```
+
+When the `imports` field maps to a relative target, the branch instead goes:
+
+```text
+  ➝ internal
+  ➝ imports-field-relative                 (ImportsFieldPlugin mapped "#util" to "./util.js";
+                                            ExtensionAliasPlugin can swap .js → .ts here)
+  ➝ relative ➝ … (same tail as the relative flow above)
+```
+
+Alias hit (`@/button` with `alias: { "@": "/src" }`) — rewrites then restarts:
+
+```text
+resolve ➝ parsed-resolve ➝ described-resolve
+  ➝ raw-resolve
+  ➝ internal-resolve                       (AliasPlugin rewrote request → "/src/button")
+  ➝ parsed-resolve ➝ … (fullySpecified forced off; AliasPlugin won't re-fire for the rewritten form)
+```
+
+`exports`-field hit inside a package (`pkg/feature` matching `"./feature"` in `exports`):
+
+```text
+… ➝ raw-module ➝ module ➝ resolve-as-module ➝ undescribed-resolve-in-package
+  ➝ resolve-in-package
+  ➝ relative                               (ExportsFieldPlugin jumped to the exports target;
+                                            main-field / main-file logic is skipped)
+  ➝ described-relative ➝ raw-file ➝ file ➝ final-file ➝ existing-file ➝ resolved
+```
+
+Failure — every candidate opts out (`callback()`) and no handler ever short-circuits with a result. `noResolve` fires once, for the top-level request:
+
+```text
+… ➝ final-file
+       ✗ FileExistsPlugin: ENOENT  (opts out; no extension candidates left)
+  ⇠ bail hooks unwind, each tapped handler has already tried its alternatives
+  ⇒ top-level resolve() returns no result
+  ⇒ resolver.hooks.noResolve(request, error)    (ResultPlugin never fires)
+```
+
+#### Hook examples
+
+Trace every pipeline step and observe failures via the lifecycle hooks:
+
+```js
+resolver.hooks.resolveStep.tap("Trace", (hook, request) => {
+	console.log(`[step] ${hook.name}: ${request.request} @ ${request.path}`);
+});
+resolver.hooks.noResolve.tap("Trace", (request, error) => {
+	console.log(`[fail] ${request.request}: ${error.message}`);
+});
+resolver.hooks.result.tapAsync("Trace", (request, _ctx, callback) => {
+	console.log(`[done] ${request.path}`);
+	callback();
+});
+```
+
+Short-circuit at `file` to redirect any `.css` request to a stub without continuing the pipeline:
+
+```js
+class StubCssPlugin {
+	apply(resolver) {
+		resolver
+			.getHook("file")
+			.tapAsync("StubCssPlugin", (request, _ctx, callback) => {
+				if (!request.path || !request.path.endsWith(".css")) return callback();
+				callback(null, { ...request, path: require.resolve("./empty.css") });
+			});
+	}
+}
+```
+
+Forward to a different hook with `doResolve` to restart resolution with a rewritten request — see `MyLibSrcPlugin` in [Writing a Custom Plugin](#writing-a-custom-plugin) for the canonical pattern (`getHook("described-resolve")` → `doResolve(ensureHook("resolve"), …)`).
+
+### Writing a Custom Plugin
+
+The example below adds a plugin that rewrites any request starting with `my-lib/` to `my-lib/src/`. It taps the `described-resolve` hook (after the description file has been located) and forwards the rewritten request to `resolve`, so the pipeline restarts with the new request.
+
+```js
+const fs = require("fs");
+const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
+
+class MyLibSrcPlugin {
+	apply(resolver) {
+		const target = resolver.ensureHook("resolve");
+		resolver
+			.getHook("described-resolve")
+			.tapAsync("MyLibSrcPlugin", (request, resolveContext, callback) => {
+				if (!request.request || !request.request.startsWith("my-lib/")) {
+					return callback();
+				}
+				const newRequest = {
+					...request,
+					request: request.request.replace(/^my-lib\//, "my-lib/src/"),
+				};
+				resolver.doResolve(
+					target,
+					newRequest,
+					"rewrote my-lib → my-lib/src",
+					resolveContext,
+					callback,
+				);
+			});
+	}
+}
+
+const myResolver = ResolverFactory.createResolver({
+	fileSystem: new CachedInputFileSystem(fs, 4000),
+	extensions: [".js", ".json"],
+	plugins: [new MyLibSrcPlugin()],
+});
+```
+
+Tips for writing your own plugin:
+
+- Call `callback()` with no arguments to pass the request on to the next tapped handler at the same `source` hook. This is how you "opt out" when a request doesn't apply.
+- Call `resolver.doResolve(target, newRequest, message, resolveContext, callback)` to continue the pipeline at a different hook with a (possibly modified) request.
+- Return early with `callback(null, result)` to short-circuit with a specific result, or `callback(err)` to fail the resolve.
+- See [Hooks](#hooks) for the full list of pipeline hooks, their order, and the `before-` / `after-` stage modifiers. `lib/ResolverFactory.js` has the exact wiring under `//// pipeline ////`.
+
+## Escaping
+
+It's allowed to escape `#` as `\0#` to avoid parsing it as fragment.
+
+enhanced-resolve will try to resolve requests containing `#` as path and as fragment, so it will automatically figure out if `./some#thing` means `.../some.js#thing` or `.../some#thing.js`. When a `#` is resolved as path it will be escaped in the result. Here: `.../some\0#thing.js`.
+
+## Tests
+
+```sh
+npm run test
+```
+
+## Passing options from webpack
+
+If you are using `webpack`, and you want to pass custom options to `enhanced-resolve`, the options are passed from the `resolve` key of your webpack configuration e.g.:
+
+```
+resolve: {
+  extensions: ['.js', '.jsx'],
+  modules: [path.resolve(__dirname, 'src'), 'node_modules'],
+  plugins: [new DirectoryNamedWebpackPlugin()]
+  ...
+},
+```
+
+## License
+
+Copyright (c) 2012-2019 JS Foundation and other contributors
+
+MIT (http://www.opensource.org/licenses/mit-license.php)
+
+[npm]: https://img.shields.io/npm/v/enhanced-resolve.svg
+[npm-url]: https://www.npmjs.com/package/enhanced-resolve
+[build-status]: https://github.com/webpack/enhanced-resolve/actions/workflows/test.yml/badge.svg
+[build-status-url]: https://github.com/webpack/enhanced-resolve/actions
+[codecov-badge]: https://codecov.io/gh/webpack/enhanced-resolve/branch/main/graph/badge.svg?token=6B6NxtsZc3
+[codecov-url]: https://codecov.io/gh/webpack/enhanced-resolve
+[size]: https://packagephobia.com/badge?p=enhanced-resolve
+[size-url]: https://packagephobia.com/result?p=enhanced-resolve
+[discussion]: https://img.shields.io/github/discussions/webpack/webpack
+[discussion-url]: https://github.com/webpack/webpack/discussions
Index: frontend/node_modules/enhanced-resolve/lib/AliasFieldPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/AliasFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/AliasFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,123 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+const getInnerRequest = require("./getInnerRequest");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonPrimitive} JsonPrimitive */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+// Sentinel stored in `_fieldDataCache` when a description file does not
+// contain a usable alias field object. Lets us distinguish "not cached yet"
+// from "no valid field" without calling back into `getField`.
+const NO_FIELD_OBJECT = Symbol("NoFieldObject");
+
+module.exports = class AliasFieldPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | string[]} field field
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, field, target) {
+		this.source = source;
+		this.field = field;
+		this.target = target;
+		// `this.field` is fixed for the plugin's lifetime, so caching
+		// per description-file content is safe. The cached value is either
+		// the resolved alias-map object or the `NO_FIELD_OBJECT` sentinel
+		// meaning "description file has no usable alias field".
+		/** @type {WeakMap<import("./Resolver").JsonObject, { [k: string]: JsonPrimitive } | typeof NO_FIELD_OBJECT>} */
+		this._fieldDataCache = new WeakMap();
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("AliasFieldPlugin", (request, resolveContext, callback) => {
+				if (!request.descriptionFileData) return callback();
+				const innerRequest = getInnerRequest(resolver, request);
+				if (!innerRequest) return callback();
+				const { descriptionFileData } = request;
+				let fieldData = this._fieldDataCache.get(descriptionFileData);
+				if (fieldData === undefined) {
+					const raw = DescriptionFileUtils.getField(
+						descriptionFileData,
+						this.field,
+					);
+					fieldData =
+						raw === null || typeof raw !== "object"
+							? NO_FIELD_OBJECT
+							: /** @type {{ [k: string]: JsonPrimitive }} */ (raw);
+					this._fieldDataCache.set(descriptionFileData, fieldData);
+				}
+				if (fieldData === NO_FIELD_OBJECT) {
+					if (resolveContext.log) {
+						resolveContext.log(
+							`Field '${this.field}' doesn't contain a valid alias configuration`,
+						);
+					}
+					return callback();
+				}
+				/** @type {JsonPrimitive | undefined} */
+				const data = Object.prototype.hasOwnProperty.call(
+					fieldData,
+					innerRequest,
+				)
+					? /** @type {{ [Key in string]: JsonPrimitive }} */ (fieldData)[
+							innerRequest
+						]
+					: innerRequest.startsWith("./")
+						? /** @type {{ [Key in string]: JsonPrimitive }} */ (fieldData)[
+								innerRequest.slice(2)
+							]
+						: undefined;
+				if (data === innerRequest) return callback();
+				if (data === undefined) return callback();
+				if (data === false) {
+					/** @type {ResolveRequest} */
+					const ignoreObj = {
+						...request,
+						path: false,
+					};
+					if (typeof resolveContext.yield === "function") {
+						resolveContext.yield(ignoreObj);
+						return callback(null, null);
+					}
+					return callback(null, ignoreObj);
+				}
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: /** @type {string} */ (request.descriptionFileRoot),
+					request: /** @type {string} */ (data),
+					fullySpecified: false,
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					`aliased from description file ${
+						request.descriptionFilePath
+					} with mapping '${innerRequest}' to '${/** @type {string} */ data}'`,
+					resolveContext,
+					(err, result) => {
+						if (err) return callback(err);
+
+						// Don't allow other aliasing or raw request
+						if (result === undefined) return callback(null, null);
+						callback(null, result);
+					},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/AliasPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/AliasPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/AliasPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {string | string[] | false} Alias */
+/** @typedef {{ alias: Alias, name: string, onlyModule?: boolean }} AliasOption */
+
+const { aliasResolveHandler, compileAliasOptions } = require("./AliasUtils");
+
+/**
+ * When `alias` is given as an array, the targets are tried in priority
+ * order and the first matching one wins. Tried-and-failed higher-priority
+ * targets are recorded on `resolveContext.missingDependencies` (via the
+ * downstream `FileExistsPlugin`) so that a consumer's watcher can
+ * invalidate the resolve once one of them appears. The winning target is
+ * recorded on `resolveContext.fileDependencies`; its removal triggers
+ * re-resolution, at which point the fallback target is returned.
+ *
+ * Callers that cache successful resolves (e.g. webpack's `unsafeCache`)
+ * are responsible for invalidating those entries when the tracked
+ * dependencies change -- otherwise a stale path may survive across
+ * rebuilds even though this plugin itself would return the correct
+ * fallback on a fresh resolve.
+ */
+module.exports = class AliasPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {AliasOption | AliasOption[]} options options
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, options, target) {
+		this.source = source;
+		this.options = Array.isArray(options) ? options : [options];
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		const compiled = compileAliasOptions(resolver, this.options);
+
+		resolver
+			.getHook(this.source)
+			.tapAsync("AliasPlugin", (request, resolveContext, callback) => {
+				aliasResolveHandler(
+					resolver,
+					compiled,
+					target,
+					request,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/AliasUtils.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/AliasUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/AliasUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,333 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+const { PathType, getType } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./Resolver").ResolveCallback} ResolveCallback */
+/** @typedef {string | string[] | false} Alias */
+/** @typedef {{ alias: Alias, name: string, onlyModule?: boolean }} AliasOption */
+
+/**
+ * @typedef {object} CompiledAliasOption
+ * @property {string} name original alias name
+ * @property {string} nameWithSlash name + "/" — precomputed to avoid per-resolve concat
+ * @property {Alias} alias alias target(s)
+ * @property {boolean} onlyModule normalized onlyModule flag
+ * @property {string | null} absolutePath absolute form of `name` (with slash ending), null when not absolute
+ * @property {string | null} wildcardPrefix substring before the single "*" in `name`, null when no wildcard
+ * @property {string | null} wildcardSuffix substring after the single "*" in `name`, null when no wildcard
+ * @property {number} firstCharCode first character code of `name` — used as a cheap screen on the hot path. `-1` indicates "matches any first char" (empty wildcard prefix).
+ * @property {boolean} arrayAlias true when `alias` is an array — precomputed so the hot path skips `Array.isArray`
+ */
+
+/**
+ * Bucketed view of compiled options used by `aliasResolveHandler` to avoid
+ * walking the full option list on every resolve. The `all` array preserves
+ * the legacy linear order (declaration order) for the fallback path. The
+ * `byFirstChar` map buckets options by the first char code of their `name`
+ * — each bucket preserves declaration order among its members. The
+ * `hasAnyFirstChar` flag is true when at least one option matches any
+ * first char (`firstCharCode === -1`), in which case resolve-time scans
+ * fall back to `all` to keep declaration-order semantics across buckets.
+ * The `useBuckets` flag is true only when bucketing would actually help —
+ * i.e. there are at least 2 distinct first chars AND no empty-prefix
+ * wildcard. When false, the resolve hot path skips the `Map.get` and
+ * iterates `all` directly with the per-option first-char-code screen
+ * (matching the pre-bucketing behavior). This avoids paying for `Map.get`
+ * on degenerate single-bucket lists like a long chain of aliases that
+ * all share one first char — the bucket lookup adds overhead without
+ * narrowing the candidate set, which showed up as a transient-memory
+ * regression on `pathological-deep-stack`.
+ * @typedef {object} CompiledAliasOptions
+ * @property {CompiledAliasOption[]} all declaration-ordered list
+ * @property {Map<number, CompiledAliasOption[]>} byFirstChar bucketed by first char code
+ * @property {boolean} hasAnyFirstChar true when an empty-prefix wildcard is present
+ * @property {boolean} useBuckets true when the bucket fast-path should be used at resolve time
+ */
+
+const EMPTY_LIST = /** @type {CompiledAliasOption[]} */ ([]);
+const EMPTY_COMPILED_OPTIONS = /** @type {CompiledAliasOptions} */ ({
+	all: EMPTY_LIST,
+	byFirstChar: new Map(),
+	hasAnyFirstChar: false,
+	useBuckets: false,
+});
+
+/**
+ * Precompute per-option strings used on every resolve so the hot path in
+ * `aliasResolveHandler` does no string concatenation / split work per entry.
+ * Called once per plugin apply — the returned structure is stable for the
+ * lifetime of the resolver.
+ *
+ * Beyond the per-option precompute step, this also partitions the list into
+ * a `byFirstChar` map so that, when no "empty-prefix" wildcards are
+ * present, the resolve-time scan only walks options whose `name` starts
+ * with the same char as the current request. For large alias lists (300+
+ * entries) this turns an O(N) screen into O(K) where K is the bucket size
+ * for the request's first char.
+ * @param {Resolver} resolver resolver
+ * @param {AliasOption[]} options options
+ * @returns {CompiledAliasOptions} compiled options
+ */
+function compileAliasOptions(resolver, options) {
+	if (options.length === 0) return EMPTY_COMPILED_OPTIONS;
+	const all = /** @type {CompiledAliasOption[]} */ (
+		Array.from({ length: options.length })
+	);
+	/** @type {Map<number, CompiledAliasOption[]>} */
+	const byFirstChar = new Map();
+	let hasAnyFirstChar = false;
+	for (let i = 0; i < options.length; i++) {
+		const item = options[i];
+		const { name } = item;
+		let absolutePath = null;
+		const type = getType(name);
+		if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) {
+			absolutePath = resolver.join(name, "_").slice(0, -1);
+		}
+		const firstStar = name.indexOf("*");
+		let wildcardPrefix = null;
+		let wildcardSuffix = null;
+		if (firstStar !== -1 && !name.includes("*", firstStar + 1)) {
+			wildcardPrefix = name.slice(0, firstStar);
+			wildcardSuffix = name.slice(firstStar + 1);
+		}
+		// firstCharCode: used by `aliasResolveHandler` to quickly skip aliases
+		// whose name can't possibly match the current innerRequest. For a plain
+		// alias (no wildcard) the first char of the name is also the first char
+		// of `nameWithSlash` and of `absolutePath` (since the latter is derived
+		// from name via `resolver.join(name, "_")`, which only appends). For a
+		// wildcard with a non-empty prefix, the first char of that prefix is
+		// also the first char of name. Only the `name === "*"` case (empty
+		// wildcard prefix) can match arbitrary first chars — encode that as -1.
+		let firstCharCode;
+		if (wildcardPrefix !== null && wildcardPrefix.length === 0) {
+			firstCharCode = -1;
+		} else {
+			firstCharCode = name.length > 0 ? name.charCodeAt(0) : -1;
+		}
+		const compiled = {
+			name,
+			nameWithSlash: `${name}/`,
+			alias: item.alias,
+			onlyModule: Boolean(item.onlyModule),
+			absolutePath,
+			wildcardPrefix,
+			wildcardSuffix,
+			firstCharCode,
+			arrayAlias: Array.isArray(item.alias),
+		};
+		all[i] = compiled;
+		if (firstCharCode === -1) {
+			hasAnyFirstChar = true;
+		} else {
+			let bucket = byFirstChar.get(firstCharCode);
+			if (bucket === undefined) {
+				bucket = [];
+				byFirstChar.set(firstCharCode, bucket);
+			}
+			bucket.push(compiled);
+		}
+	}
+	// Only enable the bucket fast-path when it would actually help. With
+	// a single bucket (all aliases share one first char, e.g. a chain of
+	// `chain-0 -> chain-1 -> …` rewrites), the resolve-time `Map.get`
+	// does no discrimination — every request lands in that one bucket
+	// or in nothing — and the lookup is overhead compared to walking
+	// `all` with the per-option first-char-code screen. Requiring 2+
+	// distinct first chars matches the cases where bucketing has
+	// measurable benefit (huge-alias-* / large-alias-list / stack-churn).
+	const useBuckets = !hasAnyFirstChar && byFirstChar.size >= 2;
+	return { all, byFirstChar, hasAnyFirstChar, useBuckets };
+}
+
+/** @typedef {(err?: null | Error, result?: null | ResolveRequest) => void} InnerCallback */
+/**
+ * @param {Resolver} resolver resolver
+ * @param {CompiledAliasOptions} options compiled options
+ * @param {ResolveStepHook} target target
+ * @param {ResolveRequest} request request
+ * @param {ResolveContext} resolveContext resolve context
+ * @param {InnerCallback} callback callback
+ * @returns {void}
+ */
+function aliasResolveHandler(
+	resolver,
+	options,
+	target,
+	request,
+	resolveContext,
+	callback,
+) {
+	if (options.all.length === 0) return callback();
+	const innerRequest = request.request || request.path;
+	if (!innerRequest) return callback();
+
+	// Precompute values used in the inner scan loop so we don't recompute
+	// them per option. This is meaningful when `options` has hundreds of
+	// entries (e.g. monorepos with generated alias lists) — see the
+	// `huge-alias-list` / `huge-alias-miss` benchmarks.
+	const innerFirstCharCode = innerRequest.charCodeAt(0);
+	const hasRequestString = Boolean(request.request);
+
+	// Dispatch through the first-char-code bucket when it actually
+	// narrows the candidate set (`useBuckets` requires 2+ distinct
+	// first chars and no empty-prefix wildcard). When the field has
+	// only one first-char bucket — e.g. a long chain of `chain-N`
+	// aliases that all start with the same char — every request lands
+	// in that one bucket or nothing, so `Map.get` is overhead vs. just
+	// walking `all` with the per-option char-code screen. Walking
+	// `all` also matches the pre-bucketing behavior and keeps the
+	// `pathological-deep-stack` allocation profile flat.
+	let scan;
+	if (options.useBuckets) {
+		const bucket = options.byFirstChar.get(innerFirstCharCode);
+		if (bucket === undefined) return callback();
+		scan = bucket;
+	} else {
+		scan = options.all;
+	}
+
+	forEachBail(
+		scan,
+		(item, callback) => {
+			// Char-code screen left in for the fallback (`options.all`) path
+			// where the bucket dispatch above wasn't usable. In the bucket
+			// path this is always true and folds into a no-op.
+			const { firstCharCode } = item;
+			if (firstCharCode !== -1 && firstCharCode !== innerFirstCharCode) {
+				return callback();
+			}
+
+			/** @type {boolean} */
+			let shouldStop = false;
+
+			// For absolute-name aliases, accept the normalized
+			// `absolutePath` form as well as the raw `nameWithSlash`.
+			// `nameWithSlash` unconditionally appends `/`, so a raw
+			// windows request with native backslashes
+			// (e.g. `C:\\abs\\foo\\baz` against `name: "C:\\abs\\foo"`)
+			// otherwise fails `startsWith("C:\\abs\\foo/")` and is
+			// silently skipped. Mirroring the `absolutePath` check in
+			// both branches closes the gap without changing any
+			// existing matches.
+			const { absolutePath } = item;
+			const matchRequest =
+				innerRequest === item.name ||
+				(!item.onlyModule &&
+					((hasRequestString && innerRequest.startsWith(item.nameWithSlash)) ||
+						(absolutePath !== null && innerRequest.startsWith(absolutePath))));
+
+			const matchWildcard = !item.onlyModule && item.wildcardPrefix !== null;
+
+			if (matchRequest || matchWildcard) {
+				/**
+				 * @param {Alias} alias alias
+				 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+				 * @returns {void}
+				 */
+				const resolveWithAlias = (alias, callback) => {
+					if (alias === false) {
+						/** @type {ResolveRequest} */
+						const ignoreObj = {
+							...request,
+							path: false,
+						};
+						if (typeof resolveContext.yield === "function") {
+							resolveContext.yield(ignoreObj);
+							return callback(null, null);
+						}
+						return callback(null, ignoreObj);
+					}
+
+					let newRequestStr;
+
+					if (
+						matchWildcard &&
+						innerRequest.startsWith(
+							/** @type {string} */ (item.wildcardPrefix),
+						) &&
+						innerRequest.endsWith(/** @type {string} */ (item.wildcardSuffix))
+					) {
+						const match = innerRequest.slice(
+							/** @type {string} */ (item.wildcardPrefix).length,
+							innerRequest.length -
+								/** @type {string} */ (item.wildcardSuffix).length,
+						);
+						newRequestStr = alias.toString().replace("*", match);
+					}
+
+					if (
+						matchRequest &&
+						innerRequest !== alias &&
+						!innerRequest.startsWith(`${alias}/`)
+					) {
+						/** @type {string} */
+						const remainingRequest = innerRequest.slice(item.name.length);
+						newRequestStr = alias + remainingRequest;
+					}
+
+					if (newRequestStr !== undefined) {
+						shouldStop = true;
+						/** @type {ResolveRequest} */
+						const obj = {
+							...request,
+							request: newRequestStr,
+							fullySpecified: false,
+						};
+						return resolver.doResolve(
+							target,
+							obj,
+							`aliased with mapping '${item.name}': '${alias}' to '${newRequestStr}'`,
+							resolveContext,
+							(err, result) => {
+								if (err) return callback(err);
+								if (result) return callback(null, result);
+								return callback();
+							},
+						);
+					}
+					return callback();
+				};
+
+				/**
+				 * @param {(null | Error)=} err error
+				 * @param {(null | ResolveRequest)=} result result
+				 * @returns {void}
+				 */
+				const stoppingCallback = (err, result) => {
+					if (err) return callback(err);
+
+					if (result) return callback(null, result);
+					// Don't allow other aliasing or raw request
+					if (shouldStop) return callback(null, null);
+					return callback();
+				};
+
+				if (item.arrayAlias) {
+					return forEachBail(
+						/** @type {string[]} */ (item.alias),
+						resolveWithAlias,
+						stoppingCallback,
+					);
+				}
+				return resolveWithAlias(item.alias, stoppingCallback);
+			}
+
+			return callback();
+		},
+		callback,
+	);
+}
+
+module.exports.aliasResolveHandler = aliasResolveHandler;
+module.exports.compileAliasOptions = compileAliasOptions;
Index: frontend/node_modules/enhanced-resolve/lib/AppendPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/AppendPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/AppendPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class AppendPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string} appending appending
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, appending, target) {
+		this.source = source;
+		this.appending = appending;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("AppendPlugin", (request, resolveContext, callback) => {
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: request.path + this.appending,
+					relativePath:
+						request.relativePath && request.relativePath + this.appending,
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					this.appending,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,686 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+// eslint-disable-next-line n/prefer-global/process
+const { nextTick } = require("process");
+
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").PathLike} PathLike */
+/** @typedef {import("./Resolver").PathOrFileDescriptor} PathOrFileDescriptor */
+/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
+/** @typedef {FileSystem & SyncFileSystem} BaseFileSystem */
+
+/**
+ * @template T
+ * @typedef {import("./Resolver").FileSystemCallback<T>} FileSystemCallback<T>
+ */
+
+/**
+ * @param {string} path path
+ * @returns {string} dirname
+ */
+const dirname = (path) => {
+	let idx = path.length - 1;
+	while (idx >= 0) {
+		const char = path.charCodeAt(idx);
+		// slash or backslash
+		if (char === 47 || char === 92) break;
+		idx--;
+	}
+	if (idx < 0) return "";
+	return path.slice(0, idx);
+};
+
+/**
+ * @template T
+ * @param {FileSystemCallback<T>[]} callbacks callbacks
+ * @param {Error | null} err error
+ * @param {T} result result
+ */
+const runCallbacks = (callbacks, err, result) => {
+	if (callbacks.length === 1) {
+		callbacks[0](err, result);
+		callbacks.length = 0;
+		return;
+	}
+	let error;
+	for (const callback of callbacks) {
+		try {
+			callback(err, result);
+		} catch (err) {
+			if (!error) error = err;
+		}
+	}
+	callbacks.length = 0;
+	if (error) throw error;
+};
+
+// eslint-disable-next-line jsdoc/reject-function-type
+/** @typedef {Function} EXPECTED_FUNCTION */
+// eslint-disable-next-line jsdoc/reject-any-type
+/** @typedef {any} EXPECTED_ANY */
+
+class OperationMergerBackend {
+	/**
+	 * @param {EXPECTED_FUNCTION | undefined} provider async method in filesystem
+	 * @param {EXPECTED_FUNCTION | undefined} syncProvider sync method in filesystem
+	 * @param {BaseFileSystem} providerContext call context for the provider methods
+	 */
+	constructor(provider, syncProvider, providerContext) {
+		this._provider = provider;
+		this._syncProvider = syncProvider;
+		this._providerContext = providerContext;
+		this._activeAsyncOperations = new Map();
+
+		this.provide = this._provider
+			? // Comment to align jsdoc
+				/**
+				 * @param {PathLike | PathOrFileDescriptor} path path
+				 * @param {object | FileSystemCallback<EXPECTED_ANY> | undefined} options options
+				 * @param {FileSystemCallback<EXPECTED_ANY>=} callback callback
+				 * @returns {EXPECTED_ANY} result
+				 */
+				(path, options, callback) => {
+					if (typeof options === "function") {
+						callback =
+							/** @type {FileSystemCallback<EXPECTED_ANY>} */
+							(options);
+						options = undefined;
+					}
+					if (
+						typeof path !== "string" &&
+						!Buffer.isBuffer(path) &&
+						!(path instanceof URL) &&
+						typeof path !== "number"
+					) {
+						/** @type {EXPECTED_FUNCTION} */
+						(callback)(
+							new TypeError("path must be a string, Buffer, URL or number"),
+						);
+						return;
+					}
+					if (options) {
+						return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
+							this._providerContext,
+							path,
+							options,
+							callback,
+						);
+					}
+					let callbacks = this._activeAsyncOperations.get(path);
+					if (callbacks) {
+						callbacks.push(callback);
+						return;
+					}
+					this._activeAsyncOperations.set(path, (callbacks = [callback]));
+					/** @type {EXPECTED_FUNCTION} */
+					(provider)(
+						path,
+						/**
+						 * @param {Error} err error
+						 * @param {EXPECTED_ANY} result result
+						 */
+						(err, result) => {
+							this._activeAsyncOperations.delete(path);
+							runCallbacks(callbacks, err, result);
+						},
+					);
+				}
+			: null;
+		this.provideSync = this._syncProvider
+			? // Comment to align jsdoc
+				/**
+				 * @param {PathLike | PathOrFileDescriptor} path path
+				 * @param {object=} options options
+				 * @returns {EXPECTED_ANY} result
+				 */
+				(path, options) =>
+					/** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
+						this._providerContext,
+						path,
+						options,
+					)
+			: null;
+	}
+
+	purge() {}
+
+	purgeParent() {}
+}
+
+/*
+
+IDLE:
+	insert data: goto SYNC
+
+SYNC:
+	before provide: run ticks
+	event loop tick: goto ASYNC_ACTIVE
+
+ASYNC:
+	timeout: run tick, goto ASYNC_PASSIVE
+
+ASYNC_PASSIVE:
+	before provide: run ticks
+
+IDLE --[insert data]--> SYNC --[event loop tick]--> ASYNC_ACTIVE --[interval tick]-> ASYNC_PASSIVE
+                                                          ^                             |
+                                                          +---------[insert data]-------+
+*/
+
+const STORAGE_MODE_IDLE = 0;
+const STORAGE_MODE_SYNC = 1;
+const STORAGE_MODE_ASYNC = 2;
+
+/**
+ * @callback Provide
+ * @param {PathLike | PathOrFileDescriptor} path path
+ * @param {EXPECTED_ANY} options options
+ * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
+ * @returns {void}
+ */
+
+class CacheBackend {
+	/**
+	 * @param {number} duration max cache duration of items
+	 * @param {EXPECTED_FUNCTION | undefined} provider async method
+	 * @param {EXPECTED_FUNCTION | undefined} syncProvider sync method
+	 * @param {BaseFileSystem} providerContext call context for the provider methods
+	 */
+	constructor(duration, provider, syncProvider, providerContext) {
+		this._duration = duration;
+		this._provider = provider;
+		this._syncProvider = syncProvider;
+		this._providerContext = providerContext;
+		/** @type {Map<string, FileSystemCallback<EXPECTED_ANY>[]>} */
+		this._activeAsyncOperations = new Map();
+		/** @type {Map<string, { err: Error | null, result?: EXPECTED_ANY, level: Set<string> }>} */
+		this._data = new Map();
+		/** @type {Set<string>[]} */
+		this._levels = [];
+		for (let i = 0; i < 10; i++) this._levels.push(new Set());
+		if (duration !== Infinity) {
+			for (let i = 5000; i < duration; i += 500) {
+				this._levels.push(new Set());
+			}
+		}
+		this._currentLevel = 0;
+		this._tickInterval = Math.floor(duration / this._levels.length);
+		/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
+		this._mode = STORAGE_MODE_IDLE;
+
+		/** @type {NodeJS.Timeout | undefined} */
+		this._timeout = undefined;
+		/** @type {number | undefined} */
+		this._nextDecay = undefined;
+
+		// eslint-disable-next-line no-warning-comments
+		// @ts-ignore
+		this.provide = provider ? this.provide.bind(this) : null;
+		// eslint-disable-next-line no-warning-comments
+		// @ts-ignore
+		this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
+	}
+
+	/**
+	 * @param {PathLike | PathOrFileDescriptor} path path
+	 * @param {EXPECTED_ANY} options options
+	 * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
+	 * @returns {void}
+	 */
+	provide(path, options, callback) {
+		if (typeof options === "function") {
+			callback = options;
+			options = undefined;
+		}
+		if (
+			typeof path !== "string" &&
+			!Buffer.isBuffer(path) &&
+			!(path instanceof URL) &&
+			typeof path !== "number"
+		) {
+			callback(new TypeError("path must be a string, Buffer, URL or number"));
+			return;
+		}
+		const strPath = typeof path !== "string" ? path.toString() : path;
+		if (options) {
+			return /** @type {EXPECTED_FUNCTION} */ (this._provider).call(
+				this._providerContext,
+				path,
+				options,
+				callback,
+			);
+		}
+
+		// When in sync mode we can move to async mode
+		if (this._mode === STORAGE_MODE_SYNC) {
+			this._enterAsyncMode();
+		}
+
+		// Check in cache
+		const cacheEntry = this._data.get(strPath);
+		if (cacheEntry !== undefined) {
+			if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
+			return nextTick(callback, null, cacheEntry.result);
+		}
+
+		// Check if there is already the same operation running
+		let callbacks = this._activeAsyncOperations.get(strPath);
+		if (callbacks !== undefined) {
+			callbacks.push(callback);
+			return;
+		}
+		this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
+
+		// Run the operation
+		/** @type {EXPECTED_FUNCTION} */
+		(this._provider).call(
+			this._providerContext,
+			path,
+			/**
+			 * @param {Error | null} err error
+			 * @param {EXPECTED_ANY=} result result
+			 */
+			(err, result) => {
+				this._activeAsyncOperations.delete(strPath);
+				this._storeResult(strPath, err, result);
+
+				// Enter async mode if not yet done
+				this._enterAsyncMode();
+
+				runCallbacks(
+					/** @type {FileSystemCallback<EXPECTED_ANY>[]} */ (callbacks),
+					err,
+					result,
+				);
+			},
+		);
+	}
+
+	/**
+	 * @param {PathLike | PathOrFileDescriptor} path path
+	 * @param {EXPECTED_ANY} options options
+	 * @returns {EXPECTED_ANY} result
+	 */
+	provideSync(path, options) {
+		if (
+			typeof path !== "string" &&
+			!Buffer.isBuffer(path) &&
+			!(path instanceof URL) &&
+			typeof path !== "number"
+		) {
+			throw new TypeError("path must be a string");
+		}
+		const strPath = typeof path !== "string" ? path.toString() : path;
+		if (options) {
+			return /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
+				this._providerContext,
+				path,
+				options,
+			);
+		}
+
+		// In sync mode we may have to decay some cache items
+		if (this._mode === STORAGE_MODE_SYNC) {
+			this._runDecays();
+		}
+
+		// Check in cache
+		const cacheEntry = this._data.get(strPath);
+		if (cacheEntry !== undefined) {
+			if (cacheEntry.err) throw cacheEntry.err;
+			return cacheEntry.result;
+		}
+
+		// Get all active async operations
+		// This sync operation will also complete them
+		const callbacks = this._activeAsyncOperations.get(strPath);
+		this._activeAsyncOperations.delete(strPath);
+
+		// Run the operation
+		// When in idle mode, we will enter sync mode
+		let result;
+		try {
+			result = /** @type {EXPECTED_FUNCTION} */ (this._syncProvider).call(
+				this._providerContext,
+				path,
+			);
+		} catch (err) {
+			this._storeResult(strPath, /** @type {Error} */ (err), undefined);
+			this._enterSyncModeWhenIdle();
+			if (callbacks) {
+				runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
+			}
+			throw err;
+		}
+		this._storeResult(strPath, null, result);
+		this._enterSyncModeWhenIdle();
+		if (callbacks) {
+			runCallbacks(callbacks, null, result);
+		}
+		return result;
+	}
+
+	/**
+	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
+	 */
+	purge(what) {
+		if (!what) {
+			if (this._mode !== STORAGE_MODE_IDLE) {
+				this._data.clear();
+				for (const level of this._levels) {
+					level.clear();
+				}
+				this._enterIdleMode();
+			}
+		} else if (
+			typeof what === "string" ||
+			Buffer.isBuffer(what) ||
+			what instanceof URL ||
+			typeof what === "number"
+		) {
+			const strWhat = typeof what !== "string" ? what.toString() : what;
+			for (const [key, data] of this._data) {
+				if (key.startsWith(strWhat)) {
+					this._data.delete(key);
+					data.level.delete(key);
+				}
+			}
+			if (this._data.size === 0) {
+				this._enterIdleMode();
+			}
+		} else {
+			for (const [key, data] of this._data) {
+				for (const item of what) {
+					const strItem = typeof item !== "string" ? item.toString() : item;
+					if (key.startsWith(strItem)) {
+						this._data.delete(key);
+						data.level.delete(key);
+						break;
+					}
+				}
+			}
+			if (this._data.size === 0) {
+				this._enterIdleMode();
+			}
+		}
+	}
+
+	/**
+	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
+	 */
+	purgeParent(what) {
+		if (!what) {
+			this.purge();
+		} else if (
+			typeof what === "string" ||
+			Buffer.isBuffer(what) ||
+			what instanceof URL ||
+			typeof what === "number"
+		) {
+			const strWhat = typeof what !== "string" ? what.toString() : what;
+			this.purge(dirname(strWhat));
+		} else {
+			const set = new Set();
+			for (const item of what) {
+				const strItem = typeof item !== "string" ? item.toString() : item;
+				set.add(dirname(strItem));
+			}
+			this.purge(set);
+		}
+	}
+
+	/**
+	 * @param {string} path path
+	 * @param {Error | null} err error
+	 * @param {EXPECTED_ANY} result result
+	 */
+	_storeResult(path, err, result) {
+		if (this._data.has(path)) return;
+		const level = this._levels[this._currentLevel];
+		this._data.set(path, { err, result, level });
+		level.add(path);
+	}
+
+	_decayLevel() {
+		const nextLevel = (this._currentLevel + 1) % this._levels.length;
+		const decay = this._levels[nextLevel];
+		this._currentLevel = nextLevel;
+		for (const item of decay) {
+			this._data.delete(item);
+		}
+		decay.clear();
+		if (this._data.size === 0) {
+			this._enterIdleMode();
+		} else {
+			/** @type {number} */
+			(this._nextDecay) += this._tickInterval;
+		}
+	}
+
+	_runDecays() {
+		while (
+			/** @type {number} */ (this._nextDecay) <= Date.now() &&
+			this._mode !== STORAGE_MODE_IDLE
+		) {
+			this._decayLevel();
+		}
+	}
+
+	_enterAsyncMode() {
+		let timeout = 0;
+		switch (this._mode) {
+			case STORAGE_MODE_ASYNC:
+				return;
+			case STORAGE_MODE_IDLE:
+				this._nextDecay = Date.now() + this._tickInterval;
+				timeout = this._tickInterval;
+				break;
+			case STORAGE_MODE_SYNC:
+				this._runDecays();
+				// _runDecays may change the mode
+				if (
+					/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
+					(this._mode) === STORAGE_MODE_IDLE
+				) {
+					return;
+				}
+				timeout = Math.max(
+					0,
+					/** @type {number} */ (this._nextDecay) - Date.now(),
+				);
+				break;
+		}
+		this._mode = STORAGE_MODE_ASYNC;
+		// When duration is Infinity, cache entries never expire, so there
+		// is no need to schedule a decay timer.
+		if (this._duration === Infinity) {
+			return;
+		}
+		const ref = setTimeout(() => {
+			this._mode = STORAGE_MODE_SYNC;
+			this._runDecays();
+		}, timeout);
+		if (ref.unref) ref.unref();
+		this._timeout = ref;
+	}
+
+	_enterSyncModeWhenIdle() {
+		if (this._mode === STORAGE_MODE_IDLE) {
+			this._mode = STORAGE_MODE_SYNC;
+			this._nextDecay = Date.now() + this._tickInterval;
+		}
+	}
+
+	_enterIdleMode() {
+		this._mode = STORAGE_MODE_IDLE;
+		this._nextDecay = undefined;
+		if (this._timeout) clearTimeout(this._timeout);
+	}
+}
+
+/**
+ * @template {EXPECTED_FUNCTION} Provider
+ * @template {EXPECTED_FUNCTION} AsyncProvider
+ * @template FileSystem
+ * @param {number} duration duration in ms files are cached
+ * @param {Provider | undefined} provider provider
+ * @param {AsyncProvider | undefined} syncProvider sync provider
+ * @param {BaseFileSystem} providerContext provider context
+ * @returns {OperationMergerBackend | CacheBackend} backend
+ */
+const createBackend = (duration, provider, syncProvider, providerContext) => {
+	if (duration > 0) {
+		return new CacheBackend(duration, provider, syncProvider, providerContext);
+	}
+	return new OperationMergerBackend(provider, syncProvider, providerContext);
+};
+
+module.exports = class CachedInputFileSystem {
+	/**
+	 * @param {BaseFileSystem} fileSystem file system
+	 * @param {number} duration duration in ms files are cached
+	 */
+	constructor(fileSystem, duration) {
+		this.fileSystem = fileSystem;
+
+		this._lstatBackend = createBackend(
+			duration,
+			this.fileSystem.lstat,
+			this.fileSystem.lstatSync,
+			this.fileSystem,
+		);
+		const lstat = this._lstatBackend.provide;
+		this.lstat = /** @type {FileSystem["lstat"]} */ (lstat);
+		const lstatSync = this._lstatBackend.provideSync;
+		this.lstatSync = /** @type {SyncFileSystem["lstatSync"]} */ (lstatSync);
+
+		this._statBackend = createBackend(
+			duration,
+			this.fileSystem.stat,
+			this.fileSystem.statSync,
+			this.fileSystem,
+		);
+		const stat = this._statBackend.provide;
+		this.stat = /** @type {FileSystem["stat"]} */ (stat);
+		const statSync = this._statBackend.provideSync;
+		this.statSync = /** @type {SyncFileSystem["statSync"]} */ (statSync);
+
+		this._readdirBackend = createBackend(
+			duration,
+			this.fileSystem.readdir,
+			this.fileSystem.readdirSync,
+			this.fileSystem,
+		);
+		const readdir = this._readdirBackend.provide;
+		this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
+		const readdirSync = this._readdirBackend.provideSync;
+		this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (
+			readdirSync
+		);
+
+		this._readFileBackend = createBackend(
+			duration,
+			this.fileSystem.readFile,
+			this.fileSystem.readFileSync,
+			this.fileSystem,
+		);
+		const readFile = this._readFileBackend.provide;
+		this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
+		const readFileSync = this._readFileBackend.provideSync;
+		this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (
+			readFileSync
+		);
+
+		this._readJsonBackend = createBackend(
+			duration,
+			// prettier-ignore
+			this.fileSystem.readJson ||
+				(this.readFile &&
+					(
+						/**
+						 * @param {string} path path
+						 * @param {FileSystemCallback<EXPECTED_ANY>} callback callback
+						 */
+						(path, callback) => {
+							this.readFile(path, (err, buffer) => {
+								if (err) return callback(err);
+								if (!buffer || buffer.length === 0)
+									{return callback(new Error("No file content"));}
+								let data;
+								try {
+									data = JSON.parse(buffer.toString("utf8"));
+								} catch (err_) {
+									return callback(/** @type {Error} */ (err_));
+								}
+								callback(null, data);
+							});
+						})
+				),
+			// prettier-ignore
+			this.fileSystem.readJsonSync ||
+				(this.readFileSync &&
+					(
+						/**
+						 * @param {string} path path
+						 * @returns {EXPECTED_ANY} result
+						 */
+						(path) => {
+							const buffer = this.readFileSync(path);
+							const data = JSON.parse(buffer.toString("utf8"));
+							return data;
+						}
+				 )),
+			this.fileSystem,
+		);
+		const readJson = this._readJsonBackend.provide;
+		this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
+		const readJsonSync = this._readJsonBackend.provideSync;
+		this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (
+			readJsonSync
+		);
+
+		this._readlinkBackend = createBackend(
+			duration,
+			this.fileSystem.readlink,
+			this.fileSystem.readlinkSync,
+			this.fileSystem,
+		);
+		const readlink = this._readlinkBackend.provide;
+		this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
+		const readlinkSync = this._readlinkBackend.provideSync;
+		this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (
+			readlinkSync
+		);
+
+		this._realpathBackend = createBackend(
+			duration,
+			this.fileSystem.realpath,
+			this.fileSystem.realpathSync,
+			this.fileSystem,
+		);
+		const realpath = this._realpathBackend.provide;
+		this.realpath = /** @type {FileSystem["realpath"]} */ (realpath);
+		const realpathSync = this._realpathBackend.provideSync;
+		this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ (
+			realpathSync
+		);
+	}
+
+	/**
+	 * @param {(string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>)=} what what to purge
+	 */
+	purge(what) {
+		this._statBackend.purge(what);
+		this._lstatBackend.purge(what);
+		this._readdirBackend.purgeParent(what);
+		this._readFileBackend.purge(what);
+		this._readlinkBackend.purge(what);
+		this._readJsonBackend.purge(what);
+		this._realpathBackend.purge(what);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/CloneBasenamePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/CloneBasenamePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/CloneBasenamePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,51 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class CloneBasenamePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("CloneBasenamePlugin", (request, resolveContext, callback) => {
+				const requestPath = /** @type {string} */ (request.path);
+				const filename = resolver.basename(requestPath);
+				const filePath = resolver.join(requestPath, filename);
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: filePath,
+					relativePath:
+						request.relativePath &&
+						resolver.join(request.relativePath, filename),
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					`using path: ${filePath}`,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ConditionalPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ConditionalPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ConditionalPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,59 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class ConditionalPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {Partial<ResolveRequest>} test compare object
+	 * @param {string | null} message log message
+	 * @param {boolean} allowAlternatives when false, do not continue with the current step when "test" matches
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, test, message, allowAlternatives, target) {
+		this.source = source;
+		this.test = test;
+		this.message = message;
+		this.allowAlternatives = allowAlternatives;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		const { test, message, allowAlternatives } = this;
+		const keys = /** @type {(keyof ResolveRequest)[]} */ (Object.keys(test));
+		resolver
+			.getHook(this.source)
+			.tapAsync("ConditionalPlugin", (request, resolveContext, callback) => {
+				for (const prop of keys) {
+					if (request[prop] !== test[prop]) return callback();
+				}
+				resolver.doResolve(
+					target,
+					request,
+					message,
+					resolveContext,
+					allowAlternatives
+						? callback
+						: (err, result) => {
+								if (err) return callback(err);
+
+								// Don't allow other alternatives
+								if (result === undefined) return callback(null, null);
+								callback(null, result);
+							},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,103 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+const BACKSLASH_G = /\\/g;
+
+module.exports = class DescriptionFilePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string[]} filenames filenames
+	 * @param {boolean} pathIsFile pathIsFile
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, filenames, pathIsFile, target) {
+		this.source = source;
+		this.filenames = filenames;
+		this.pathIsFile = pathIsFile;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync(
+				"DescriptionFilePlugin",
+				(request, resolveContext, callback) => {
+					const { path } = request;
+					if (!path) return callback();
+					const directory = this.pathIsFile
+						? DescriptionFileUtils.cdUp(path)
+						: path;
+					if (!directory) return callback();
+					DescriptionFileUtils.loadDescriptionFile(
+						resolver,
+						directory,
+						this.filenames,
+						request.descriptionFilePath
+							? {
+									path: request.descriptionFilePath,
+									content: request.descriptionFileData,
+									directory:
+										/** @type {string} */
+										(request.descriptionFileRoot),
+								}
+							: undefined,
+						resolveContext,
+						(err, result) => {
+							if (err) return callback(err);
+							if (!result) {
+								if (resolveContext.log) {
+									resolveContext.log(
+										`No description file found in ${directory} or above`,
+									);
+								}
+								return callback();
+							}
+							const rawRelative = path.slice(result.directory.length);
+							const relativePath = `.${
+								rawRelative.includes("\\")
+									? rawRelative.replace(BACKSLASH_G, "/")
+									: rawRelative
+							}`;
+							/** @type {ResolveRequest} */
+							const obj = {
+								...request,
+								descriptionFilePath: result.path,
+								descriptionFileData: result.content,
+								descriptionFileRoot: result.directory,
+								relativePath,
+							};
+							resolver.doResolve(
+								target,
+								obj,
+								`using description file: ${result.path} (relative path: ${relativePath})`,
+								resolveContext,
+								(err, result) => {
+									if (err) return callback(err);
+
+									// Don't allow other processing
+									if (result === undefined) return callback(null, null);
+									callback(null, result);
+								},
+							);
+						},
+					);
+				},
+			);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/DescriptionFileUtils.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/DescriptionFileUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/DescriptionFileUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,231 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonObject} JsonObject */
+/** @typedef {import("./Resolver").JsonValue} JsonValue */
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+
+/**
+ * @typedef {object} DescriptionFileInfo
+ * @property {JsonObject=} content content
+ * @property {string} path path
+ * @property {string} directory directory
+ */
+
+/**
+ * @callback ErrorFirstCallback
+ * @param {Error | null=} error
+ * @param {DescriptionFileInfo=} result
+ */
+
+/**
+ * @typedef {object} Result
+ * @property {string} path path to description file
+ * @property {string} directory directory of description file
+ * @property {JsonObject} content content of description file
+ */
+
+const CHAR_SLASH = 47;
+const CHAR_BACKSLASH = 92;
+
+/**
+ * Walk up one directory. Called once per package-root candidate and once per
+ * `described-resolve` (to find the enclosing description file), so it's on
+ * the resolver's hot path.
+ *
+ * Previous implementation called `lastIndexOf("/")` and `lastIndexOf("\\")`
+ * separately and then picked the larger. For any non-trivial directory
+ * string on POSIX, `lastIndexOf("\\")` scans the full string just to return
+ * -1. A single reverse char-code scan does the same work in one pass.
+ *
+ * Any single-character directory is treated as a root — `directory.length
+ * <= 1` collapses the `"/"`, `"\\"` and `""` branches into one compare.
+ * Without the `"\\"` case, `cdUp("\\")` (reached from a UNC root or a DOS
+ * device path like `\\?\…`) would return itself via `slice(0, i || 1)`
+ * and trap `loadDescriptionFile` in an infinite loop. Once single-char
+ * roots are filtered up front, the reverse scan always produces a
+ * strictly shorter string.
+ * @param {string} directory directory
+ * @returns {string | null} parent directory or null
+ */
+function cdUp(directory) {
+	if (directory.length <= 1) return null;
+	for (let i = directory.length - 1; i >= 0; i--) {
+		const code = directory.charCodeAt(i);
+		if (code === CHAR_SLASH || code === CHAR_BACKSLASH) {
+			return directory.slice(0, i || 1);
+		}
+	}
+	return null;
+}
+
+/**
+ * @param {Resolver} resolver resolver
+ * @param {string} directory directory
+ * @param {string[]} filenames filenames
+ * @param {DescriptionFileInfo | undefined} oldInfo oldInfo
+ * @param {ResolveContext} resolveContext resolveContext
+ * @param {ErrorFirstCallback} callback callback
+ */
+function loadDescriptionFile(
+	resolver,
+	directory,
+	filenames,
+	oldInfo,
+	resolveContext,
+	callback,
+) {
+	// Hoist the per-filename iterator and the per-level done callback out
+	// of `findDescriptionFile`. They both close over `directory`, which we
+	// reassign as we walk up the tree, so the same closures keep working
+	// across every level — the previous implementation re-allocated both
+	// arrows on every recursion step, which adds up on deep walks (multiple
+	// `DescriptionFilePlugin` taps per resolve, each climbing several
+	// directories looking for `package.json`).
+	/**
+	 * @param {string} filename filename
+	 * @param {(err?: null | Error, result?: null | Result) => void} iterCallback callback
+	 * @returns {void}
+	 */
+	const iterFilename = (filename, iterCallback) => {
+		const descriptionFilePath = resolver.join(directory, filename);
+
+		/**
+		 * @param {(null | Error)=} err error
+		 * @param {JsonObject=} resolvedContent content
+		 * @returns {void}
+		 */
+		function onJson(err, resolvedContent) {
+			if (err) {
+				if (resolveContext.log) {
+					resolveContext.log(
+						`${descriptionFilePath} (directory description file): ${err}`,
+					);
+				} else {
+					err.message = `${descriptionFilePath} (directory description file): ${err}`;
+				}
+				return iterCallback(err);
+			}
+			iterCallback(null, {
+				content: /** @type {JsonObject} */ (resolvedContent),
+				directory,
+				path: descriptionFilePath,
+			});
+		}
+
+		if (resolver.fileSystem.readJson) {
+			resolver.fileSystem.readJson(descriptionFilePath, (err, content) => {
+				if (err) {
+					if (
+						typeof (/** @type {NodeJS.ErrnoException} */ (err).code) !==
+						"undefined"
+					) {
+						if (resolveContext.missingDependencies) {
+							resolveContext.missingDependencies.add(descriptionFilePath);
+						}
+						return iterCallback();
+					}
+					if (resolveContext.fileDependencies) {
+						resolveContext.fileDependencies.add(descriptionFilePath);
+					}
+					return onJson(err);
+				}
+				if (resolveContext.fileDependencies) {
+					resolveContext.fileDependencies.add(descriptionFilePath);
+				}
+				onJson(null, content);
+			});
+		} else {
+			resolver.fileSystem.readFile(descriptionFilePath, (err, content) => {
+				if (err) {
+					if (resolveContext.missingDependencies) {
+						resolveContext.missingDependencies.add(descriptionFilePath);
+					}
+					return iterCallback();
+				}
+				if (resolveContext.fileDependencies) {
+					resolveContext.fileDependencies.add(descriptionFilePath);
+				}
+
+				/** @type {JsonObject | undefined} */
+				let json;
+
+				if (content) {
+					try {
+						json = JSON.parse(content.toString());
+					} catch (/** @type {unknown} */ err_) {
+						return onJson(/** @type {Error} */ (err_));
+					}
+				} else {
+					return onJson(new Error("No content in file"));
+				}
+
+				onJson(null, json);
+			});
+		}
+	};
+	// Forward-declared so the helpers below can reference each other
+	// without falling foul of `no-use-before-define`.
+	/** @type {() => void} */
+	let findDescriptionFile;
+	/**
+	 * @param {(null | Error)=} err error
+	 * @param {(null | Result)=} result result
+	 * @returns {void}
+	 */
+	const onLevelDone = (err, result) => {
+		if (err) return callback(err);
+		if (result) return callback(null, result);
+		const dir = cdUp(directory);
+		if (!dir) {
+			return callback();
+		}
+		directory = dir;
+		return findDescriptionFile();
+	};
+	findDescriptionFile = () => {
+		if (oldInfo && oldInfo.directory === directory) {
+			// We already have info for this directory and can reuse it
+			return callback(null, oldInfo);
+		}
+		forEachBail(filenames, iterFilename, onLevelDone);
+	};
+	findDescriptionFile();
+}
+
+/**
+ * @param {JsonObject} content content
+ * @param {string | string[]} field field
+ * @returns {JsonValue | undefined} field data
+ */
+function getField(content, field) {
+	if (!content) return undefined;
+	if (Array.isArray(field)) {
+		/** @type {JsonValue} */
+		let current = content;
+		for (let j = 0; j < field.length; j++) {
+			if (current === null || typeof current !== "object") {
+				current = null;
+				break;
+			}
+			current = /** @type {JsonValue} */ (
+				/** @type {JsonObject} */
+				(current)[field[j]]
+			);
+		}
+		return current;
+	}
+	return content[field];
+}
+
+module.exports.cdUp = cdUp;
+module.exports.getField = getField;
+module.exports.loadDescriptionFile = loadDescriptionFile;
Index: frontend/node_modules/enhanced-resolve/lib/DirectoryExistsPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/DirectoryExistsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/DirectoryExistsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,68 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class DirectoryExistsPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync(
+				"DirectoryExistsPlugin",
+				(request, resolveContext, callback) => {
+					const fs = resolver.fileSystem;
+					const directory = request.path;
+					if (!directory) return callback();
+					fs.stat(directory, (err, stat) => {
+						// Combine the two miss branches: a stat failure and a
+						// "not a directory" result share the same handling — record
+						// the path on `missingDependencies`, log the right reason,
+						// then bail. The error-message ternary picks the wording
+						// that matched the failing condition.
+						if (err || !stat || !stat.isDirectory()) {
+							if (resolveContext.missingDependencies) {
+								resolveContext.missingDependencies.add(directory);
+							}
+							if (resolveContext.log) {
+								resolveContext.log(
+									err || !stat
+										? `${directory} doesn't exist`
+										: `${directory} is not a directory`,
+								);
+							}
+							return callback();
+						}
+						if (resolveContext.fileDependencies) {
+							resolveContext.fileDependencies.add(directory);
+						}
+						resolver.doResolve(
+							target,
+							request,
+							`existing directory ${directory}`,
+							resolveContext,
+							callback,
+						);
+					});
+				},
+			);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,232 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+const forEachBail = require("./forEachBail");
+const { processExportsField } = require("./util/entrypoints");
+const { parseIdentifier } = require("./util/identifier");
+const {
+	deprecatedInvalidSegmentRegEx,
+	invalidSegmentRegEx,
+} = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonObject} JsonObject */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./util/entrypoints").ExportsField} ExportsField */
+/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
+
+module.exports = class ExportsFieldPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {Set<string>} conditionNames condition names
+	 * @param {string | string[]} fieldNamePath name path
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, conditionNames, fieldNamePath, target) {
+		this.source = source;
+		this.target = target;
+		this.conditionNames = conditionNames;
+		this.fieldName = fieldNamePath;
+		// `null` is cached for description files that have no exports field,
+		// so subsequent resolves against the same package.json skip the
+		// `DescriptionFileUtils.getField` walk entirely.
+		/** @type {WeakMap<JsonObject, FieldProcessor | null>} */
+		this._fieldProcessorCache = new WeakMap();
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("ExportsFieldPlugin", (request, resolveContext, callback) => {
+				// When there is no description file, abort
+				if (!request.descriptionFileData) return callback();
+				if (
+					// When the description file is inherited from parent, abort
+					// (There is no description file inside of this package)
+					request.relativePath !== "." ||
+					request.request === undefined
+				) {
+					return callback();
+				}
+
+				const { descriptionFileData } = request;
+				const remainingRequest =
+					request.query || request.fragment
+						? (request.request === "." ? "./" : request.request) +
+							request.query +
+							request.fragment
+						: request.request;
+
+				/** @type {string[]} */
+				let paths;
+				/** @type {string | null} */
+				let usedField;
+
+				try {
+					// Look up the cached processor first. On a cache hit we
+					// avoid re-walking the description file for the exports
+					// field — and `null` is cached for description files that
+					// have no exports field at all, so those skip the read
+					// entirely. `processExportsField` can throw on a malformed
+					// `exports` map (e.g. a key without a leading `.`), so
+					// building the processor must stay inside this try/catch.
+					let fieldProcessor =
+						this._fieldProcessorCache.get(descriptionFileData);
+					if (
+						fieldProcessor === undefined &&
+						!this._fieldProcessorCache.has(descriptionFileData)
+					) {
+						const exportsField =
+							/** @type {ExportsField | null | undefined} */
+							(
+								DescriptionFileUtils.getField(
+									descriptionFileData,
+									this.fieldName,
+								)
+							);
+						fieldProcessor = exportsField
+							? processExportsField(exportsField)
+							: null;
+						this._fieldProcessorCache.set(descriptionFileData, fieldProcessor);
+					}
+					if (!fieldProcessor) return callback();
+
+					if (request.directory) {
+						return callback(
+							new Error(
+								`Resolving to directories is not possible with the exports field (request was ${remainingRequest}/)`,
+							),
+						);
+					}
+
+					[paths, usedField] = fieldProcessor(
+						remainingRequest,
+						this.conditionNames,
+					);
+				} catch (/** @type {unknown} */ err) {
+					if (resolveContext.log) {
+						resolveContext.log(
+							`Exports field in ${request.descriptionFilePath} can't be processed: ${err}`,
+						);
+					}
+					return callback(/** @type {Error} */ (err));
+				}
+
+				if (paths.length === 0) {
+					const conditions = [...this.conditionNames];
+					const conditionsStr =
+						conditions.length === 1
+							? `the condition "${conditions[0]}"`
+							: `the conditions ${JSON.stringify(conditions)}`;
+					return callback(
+						new Error(
+							`"${remainingRequest}" is not exported under ${conditionsStr} from package ${request.descriptionFileRoot} (see exports field in ${request.descriptionFilePath})`,
+						),
+					);
+				}
+
+				forEachBail(
+					paths,
+					/**
+					 * @param {string} path path
+					 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+					 * @param {number} i index
+					 * @returns {void}
+					 */
+					(path, callback, i) => {
+						const parsedIdentifier = parseIdentifier(path);
+
+						if (!parsedIdentifier) return callback();
+
+						const [relativePath, query, fragment] = parsedIdentifier;
+
+						if (!relativePath.startsWith("./")) {
+							if (paths.length === i) {
+								return callback(
+									new Error(
+										`Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
+									),
+								);
+							}
+
+							return callback();
+						}
+
+						const withoutDotSlash = relativePath.slice(2);
+						if (
+							invalidSegmentRegEx.test(withoutDotSlash) &&
+							deprecatedInvalidSegmentRegEx.test(withoutDotSlash)
+						) {
+							if (paths.length === i) {
+								return callback(
+									new Error(
+										`Invalid "exports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
+									),
+								);
+							}
+
+							return callback();
+						}
+
+						/** @type {ResolveRequest} */
+						const obj = {
+							...request,
+							request: undefined,
+							path: resolver.join(
+								/** @type {string} */ (request.descriptionFileRoot),
+								relativePath,
+							),
+							relativePath,
+							query,
+							fragment,
+						};
+
+						resolver.doResolve(
+							target,
+							obj,
+							`using exports field: ${path}`,
+							resolveContext,
+							(err, result) => {
+								if (err) return callback(err);
+								// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
+								if (result === undefined) return callback(null, null);
+								callback(null, result);
+							},
+						);
+					},
+					/**
+					 * @param {(null | Error)=} err error
+					 * @param {(null | ResolveRequest)=} result result
+					 * @returns {void}
+					 */
+					(err, result) => {
+						if (err) return callback(err);
+						// When an exports field match was found but the target file doesn't exist,
+						// return an error to prevent fallback to parent node_modules directories.
+						// Per the Node.js ESM spec, a matched exports entry that fails to resolve
+						// is a hard error, not a signal to continue searching up the directory tree.
+						// See: https://github.com/webpack/enhanced-resolve/issues/399
+						if (!result) {
+							return callback(
+								new Error(
+									`Package path ${remainingRequest} is exported from package ${request.descriptionFileRoot}, but no valid target file was found (see exports field in ${request.descriptionFilePath})`,
+								),
+							);
+						}
+						callback(null, result);
+					},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ExtensionAliasPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ExtensionAliasPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ExtensionAliasPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,129 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {{ alias: string | string[], extension: string }} ExtensionAliasOption */
+
+module.exports = class ExtensionAliasPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {ExtensionAliasOption} options options
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, options, target) {
+		this.source = source;
+		this.options = options;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		const { extension, alias } = this.options;
+		resolver
+			.getHook(this.source)
+			.tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => {
+				// Two modes of operation:
+				// - "request" mode: original request specifier still carries the
+				//   extension (e.g. user wrote `./foo.js`). We swap the extension
+				//   on `request.request` and re-resolve.
+				// - "path" mode: the specifier has already been joined into an
+				//   absolute `request.path` (e.g. produced by the imports field).
+				//   We swap the extension on `request.path` and `request.relativePath`.
+				const useRequest = request.request !== undefined;
+				const source = useRequest
+					? /** @type {string} */ (request.request)
+					: request.path;
+				if (!source || !source.endsWith(extension)) return callback();
+				const isAliasString = typeof alias === "string";
+				// Hoist the base (everything before the old extension) out of the
+				// per-alias `resolve` callback. For an array `alias`, the callback
+				// runs once per candidate extension; the base does not change
+				// between iterations, so there's no reason to recompute it.
+				const sourceBase = source.slice(0, -extension.length);
+				const relativePathBase =
+					!useRequest &&
+					request.relativePath &&
+					request.relativePath.endsWith(extension)
+						? request.relativePath.slice(0, -extension.length)
+						: null;
+				/**
+				 * @param {string} alias extension alias
+				 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+				 * @param {number=} index index
+				 * @returns {void}
+				 */
+				const resolve = (alias, callback, index) => {
+					const newValue = `${sourceBase}${alias}`;
+					const nextRequest = useRequest
+						? {
+								...request,
+								request: newValue,
+								fullySpecified: true,
+							}
+						: {
+								...request,
+								path: newValue,
+								relativePath:
+									relativePathBase !== null
+										? `${relativePathBase}${alias}`
+										: request.relativePath,
+								fullySpecified: true,
+							};
+
+					return resolver.doResolve(
+						target,
+						nextRequest,
+						`aliased from extension alias with mapping '${extension}' to '${alias}'`,
+						resolveContext,
+						(err, result) => {
+							// Throw error if we are on the last alias (for multiple aliases) and it failed, always throw if we are not an array or we have only one alias
+							if (!isAliasString && index) {
+								if (index !== this.options.alias.length) {
+									if (resolveContext.log) {
+										resolveContext.log(
+											`Failed to alias from extension alias with mapping '${extension}' to '${alias}' for '${newValue}': ${err}`,
+										);
+									}
+
+									return callback(null, result);
+								}
+
+								return callback(err, result);
+							}
+							callback(err, result);
+						},
+					);
+				};
+				/**
+				 * @param {(null | Error)=} err error
+				 * @param {(null | ResolveRequest)=} result result
+				 * @returns {void}
+				 */
+				const stoppingCallback = (err, result) => {
+					if (err) return callback(err);
+					if (result) return callback(null, result);
+					// Don't allow other aliasing or raw request
+					return callback(null, null);
+				};
+				if (isAliasString) {
+					resolve(alias, stoppingCallback);
+				} else if (alias.length > 1) {
+					forEachBail(alias, resolve, stoppingCallback);
+				} else {
+					resolve(alias[0], stoppingCallback);
+				}
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/FileExistsPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/FileExistsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/FileExistsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class FileExistsPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		const fs = resolver.fileSystem;
+		resolver
+			.getHook(this.source)
+			.tapAsync("FileExistsPlugin", (request, resolveContext, callback) => {
+				const file = request.path;
+				if (!file) return callback();
+				fs.stat(file, (err, stat) => {
+					// Combine the two miss branches: a stat failure and a
+					// "not a file" result share the same handling — record the
+					// path on `missingDependencies`, log the right reason, then
+					// bail. The error-message ternary picks the wording that
+					// matched the failing condition.
+					if (err || !stat || !stat.isFile()) {
+						if (resolveContext.missingDependencies) {
+							resolveContext.missingDependencies.add(file);
+						}
+						if (resolveContext.log) {
+							resolveContext.log(
+								err || !stat
+									? `${file} doesn't exist`
+									: `${file} is not a file`,
+							);
+						}
+						return callback();
+					}
+					if (resolveContext.fileDependencies) {
+						resolveContext.fileDependencies.add(file);
+					}
+					resolver.doResolve(
+						target,
+						request,
+						`existing file: ${file}`,
+						resolveContext,
+						callback,
+					);
+				});
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,233 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+const forEachBail = require("./forEachBail");
+const { processImportsField } = require("./util/entrypoints");
+const { parseIdentifier } = require("./util/identifier");
+const { invalidSegmentRegEx } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonObject} JsonObject */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
+/** @typedef {import("./util/entrypoints").ImportsField} ImportsField */
+
+const dotCode = ".".charCodeAt(0);
+
+module.exports = class ImportsFieldPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {Set<string>} conditionNames condition names
+	 * @param {string | string[]} fieldNamePath name path
+	 * @param {string | ResolveStepHook} targetFile target file
+	 * @param {string | ResolveStepHook} targetPackage target package
+	 */
+	constructor(
+		source,
+		conditionNames,
+		fieldNamePath,
+		targetFile,
+		targetPackage,
+	) {
+		this.source = source;
+		this.targetFile = targetFile;
+		this.targetPackage = targetPackage;
+		this.conditionNames = conditionNames;
+		this.fieldName = fieldNamePath;
+		// `null` is cached for description files that have no imports field,
+		// so subsequent resolves against the same package.json skip the
+		// `DescriptionFileUtils.getField` walk entirely.
+		/** @type {WeakMap<JsonObject, FieldProcessor | null>} */
+		this._fieldProcessorCache = new WeakMap();
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const targetFile = resolver.ensureHook(this.targetFile);
+		const targetPackage = resolver.ensureHook(this.targetPackage);
+
+		resolver
+			.getHook(this.source)
+			.tapAsync("ImportsFieldPlugin", (request, resolveContext, callback) => {
+				// When there is no description file, abort
+				if (!request.descriptionFileData || request.request === undefined) {
+					return callback();
+				}
+
+				const { descriptionFileData } = request;
+				// Skip the concat when there's nothing to append — the common
+				// case has empty query/fragment, so this avoids an allocation
+				// per resolve. Mirrors the pattern in ExportsFieldPlugin.
+				const remainingRequest =
+					request.query || request.fragment
+						? request.request + request.query + request.fragment
+						: request.request;
+
+				/** @type {string[]} */
+				let paths;
+				/** @type {string | null} */
+				let usedField;
+
+				try {
+					// Look up the cached processor first. On a cache hit we
+					// avoid re-walking the description file for the imports
+					// field — and `null` is cached for description files that
+					// have no imports field at all, so those skip the read
+					// entirely. `processImportsField` can throw on a
+					// malformed `imports` map, so building the processor must
+					// stay inside this try/catch.
+					let fieldProcessor =
+						this._fieldProcessorCache.get(descriptionFileData);
+					if (
+						fieldProcessor === undefined &&
+						!this._fieldProcessorCache.has(descriptionFileData)
+					) {
+						const importsField =
+							/** @type {ImportsField | null | undefined} */
+							(
+								DescriptionFileUtils.getField(
+									descriptionFileData,
+									this.fieldName,
+								)
+							);
+						fieldProcessor = importsField
+							? processImportsField(importsField)
+							: null;
+						this._fieldProcessorCache.set(descriptionFileData, fieldProcessor);
+					}
+					if (!fieldProcessor) return callback();
+
+					if (request.directory) {
+						return callback(
+							new Error(
+								`Resolving to directories is not possible with the imports field (request was ${remainingRequest}/)`,
+							),
+						);
+					}
+
+					[paths, usedField] = fieldProcessor(
+						remainingRequest,
+						this.conditionNames,
+					);
+				} catch (/** @type {unknown} */ err) {
+					if (resolveContext.log) {
+						resolveContext.log(
+							`Imports field in ${request.descriptionFilePath} can't be processed: ${err}`,
+						);
+					}
+					return callback(/** @type {Error} */ (err));
+				}
+
+				if (paths.length === 0) {
+					return callback(
+						new Error(
+							`Package import ${remainingRequest} is not imported from package ${request.descriptionFileRoot} (see imports field in ${request.descriptionFilePath})`,
+						),
+					);
+				}
+
+				forEachBail(
+					paths,
+					/**
+					 * @param {string} path path
+					 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+					 * @param {number} i index
+					 * @returns {void}
+					 */
+					(path, callback, i) => {
+						const parsedIdentifier = parseIdentifier(path);
+
+						if (!parsedIdentifier) return callback();
+
+						const [path_, query, fragment] = parsedIdentifier;
+
+						switch (path_.charCodeAt(0)) {
+							// should be relative
+							case dotCode: {
+								const withoutDotSlash = path_.slice(2);
+								if (invalidSegmentRegEx.test(withoutDotSlash)) {
+									if (paths.length === i) {
+										return callback(
+											new Error(
+												`Invalid "imports" target "${path}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`,
+											),
+										);
+									}
+
+									return callback();
+								}
+
+								/** @type {ResolveRequest} */
+								const obj = {
+									...request,
+									request: undefined,
+									path: resolver.join(
+										/** @type {string} */ (request.descriptionFileRoot),
+										path_,
+									),
+									relativePath: path_,
+									query,
+									fragment,
+								};
+
+								resolver.doResolve(
+									targetFile,
+									obj,
+									`using imports field: ${path}`,
+									resolveContext,
+									(err, result) => {
+										if (err) return callback(err);
+										// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
+										if (result === undefined) return callback(null, null);
+										callback(null, result);
+									},
+								);
+								break;
+							}
+
+							// package resolving
+							default: {
+								/** @type {ResolveRequest} */
+								const obj = {
+									...request,
+									request: path_,
+									relativePath: path_,
+									fullySpecified: true,
+									query,
+									fragment,
+								};
+
+								resolver.doResolve(
+									targetPackage,
+									obj,
+									`using imports field: ${path}`,
+									resolveContext,
+									(err, result) => {
+										if (err) return callback(err);
+										// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
+										if (result === undefined) return callback(null, null);
+										callback(null, result);
+									},
+								);
+							}
+						}
+					},
+					/**
+					 * @param {null | Error=} err error
+					 * @param {null | ResolveRequest=} result result
+					 * @returns {void}
+					 */
+					(err, result) => callback(err, result || null),
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/JoinRequestPartPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/JoinRequestPartPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/JoinRequestPartPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,75 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+const namespaceStartCharCode = "@".charCodeAt(0);
+
+module.exports = class JoinRequestPartPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync(
+				"JoinRequestPartPlugin",
+				(request, resolveContext, callback) => {
+					const req = request.request || "";
+					let i = req.indexOf("/", 3);
+
+					if (i >= 0 && req.charCodeAt(2) === namespaceStartCharCode) {
+						i = req.indexOf("/", i + 1);
+					}
+
+					/** @type {string} */
+					let moduleName;
+					/** @type {string} */
+					let remainingRequest;
+					/** @type {boolean} */
+					let fullySpecified;
+					if (i < 0) {
+						moduleName = req;
+						remainingRequest = ".";
+						fullySpecified = false;
+					} else {
+						moduleName = req.slice(0, i);
+						remainingRequest = `.${req.slice(i)}`;
+						fullySpecified = /** @type {boolean} */ (request.fullySpecified);
+					}
+					/** @type {ResolveRequest} */
+					const obj = {
+						...request,
+						path: resolver.join(
+							/** @type {string} */
+							(request.path),
+							moduleName,
+						),
+						relativePath:
+							request.relativePath &&
+							resolver.join(request.relativePath, moduleName),
+						request: remainingRequest,
+						fullySpecified,
+					};
+					resolver.doResolve(target, obj, null, resolveContext, callback);
+				},
+			);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/JoinRequestPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/JoinRequestPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/JoinRequestPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,45 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class JoinRequestPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("JoinRequestPlugin", (request, resolveContext, callback) => {
+				const requestPath = /** @type {string} */ (request.path);
+				const requestRequest = /** @type {string} */ (request.request);
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: resolver.join(requestPath, requestRequest),
+					relativePath:
+						request.relativePath &&
+						resolver.join(request.relativePath, requestRequest),
+					request: undefined,
+				};
+				resolver.doResolve(target, obj, null, resolveContext, callback);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/LogInfoPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/LogInfoPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/LogInfoPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class LogInfoPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 */
+	constructor(source) {
+		this.source = source;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const { source } = this;
+		resolver
+			.getHook(this.source)
+			.tapAsync("LogInfoPlugin", (request, resolveContext, callback) => {
+				if (!resolveContext.log) return callback();
+				const { log } = resolveContext;
+				const prefix = `[${source}] `;
+				if (request.path) {
+					log(`${prefix}Resolving in directory: ${request.path}`);
+				}
+				if (request.request) {
+					log(`${prefix}Resolving request: ${request.request}`);
+				}
+				if (request.module) log(`${prefix}Request is an module request.`);
+				if (request.directory) log(`${prefix}Request is a directory request.`);
+				if (request.query) {
+					log(`${prefix}Resolving request query: ${request.query}`);
+				}
+				if (request.fragment) {
+					log(`${prefix}Resolving request fragment: ${request.fragment}`);
+				}
+				if (request.descriptionFilePath) {
+					log(
+						`${prefix}Has description data from ${request.descriptionFilePath}`,
+					);
+				}
+				if (request.relativePath) {
+					log(
+						`${prefix}Relative path from description file is: ${request.relativePath}`,
+					);
+				}
+				callback();
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/MainFieldPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/MainFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/MainFieldPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,97 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonObject} JsonObject */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+/** @typedef {{ name: string | string[], forceRelative: boolean }} MainFieldOptions */
+
+const alreadyTriedMainField = Symbol("alreadyTriedMainField");
+
+// Sentinel cached for description files where the main field resolves to a
+// value we cannot use (missing, non-string, ".", "./"). Cheaper to store and
+// check than to re-walk the description file on every resolve.
+const NO_MAIN = Symbol("NoMain");
+
+module.exports = class MainFieldPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {MainFieldOptions} options options
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, options, target) {
+		this.source = source;
+		this.options = options;
+		this.target = target;
+		// Cache the resolved `mainModule` per description-file content. The
+		// options (`name`, `forceRelative`) are fixed for this plugin
+		// instance, so caching against content alone is safe. Stores either
+		// the ready-to-use request string or the `NO_MAIN` sentinel.
+		/** @type {WeakMap<JsonObject, string | typeof NO_MAIN>} */
+		this._mainModuleCache = new WeakMap();
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("MainFieldPlugin", (request, resolveContext, callback) => {
+				if (
+					request.path !== request.descriptionFileRoot ||
+					/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
+					(request)[alreadyTriedMainField] === request.descriptionFilePath ||
+					!request.descriptionFilePath
+				) {
+					return callback();
+				}
+				const descFileData = /** @type {JsonObject} */ (
+					request.descriptionFileData
+				);
+				let mainModule = this._mainModuleCache.get(descFileData);
+				if (mainModule === undefined) {
+					let raw =
+						/** @type {string | null | undefined} */
+						(DescriptionFileUtils.getField(descFileData, this.options.name));
+					if (!raw || typeof raw !== "string" || raw === "." || raw === "./") {
+						this._mainModuleCache.set(descFileData, NO_MAIN);
+						return callback();
+					}
+					if (this.options.forceRelative && !/^\.\.?\//.test(raw)) {
+						raw = `./${raw}`;
+					}
+					mainModule = raw;
+					this._mainModuleCache.set(descFileData, mainModule);
+				} else if (mainModule === NO_MAIN) {
+					return callback();
+				}
+				const filename = resolver.basename(request.descriptionFilePath);
+				/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
+				const obj = {
+					...request,
+					request: mainModule,
+					module: false,
+					directory: mainModule.endsWith("/"),
+					[alreadyTriedMainField]: request.descriptionFilePath,
+				};
+				return resolver.doResolve(
+					target,
+					obj,
+					`use ${mainModule} from ${this.options.name} in ${filename}`,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ModulesInHierachicDirectoriesPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ModulesInHierachicDirectoriesPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ModulesInHierachicDirectoriesPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+// TODO remove in next major
+module.exports = require("./ModulesInHierarchicalDirectoriesPlugin");
Index: frontend/node_modules/enhanced-resolve/lib/ModulesInHierarchicalDirectoriesPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ModulesInHierarchicalDirectoriesPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ModulesInHierarchicalDirectoriesPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { modulesResolveHandler } = require("./ModulesUtils");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class ModulesInHierarchicalDirectoriesPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | string[]} directories directories
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, directories, target) {
+		this.source = source;
+		this.directories = /** @type {string[]} */ [...directories];
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync(
+				"ModulesInHierarchicalDirectoriesPlugin",
+				(request, resolveContext, callback) => {
+					modulesResolveHandler(
+						resolver,
+						this.directories,
+						target,
+						request,
+						resolveContext,
+						callback,
+					);
+				},
+			);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ModulesInRootPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ModulesInRootPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ModulesInRootPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class ModulesInRootPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string} path path
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, path, target) {
+		this.source = source;
+		this.path = path;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("ModulesInRootPlugin", (request, resolveContext, callback) => {
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: this.path,
+					request: `./${request.request}`,
+					module: false,
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					`looking for modules in ${this.path}`,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ModulesUtils.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ModulesUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ModulesUtils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,132 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+const { getPathsCached } = require("./getPaths");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+/** @typedef {(err?: null | Error, result?: null | ResolveRequest) => void} InnerCallback */
+
+/**
+ * Per-(directories-array) cache of the flat `addrs` list produced for a given
+ * `request.path`. For a fixed directories configuration the fan-out of
+ * `ancestor × directory` is deterministic per request.path, and many resolves
+ * share the same starting directory (sibling files in one project, loops over
+ * a batch of imports, etc.) — caching avoids the `getPaths` regex split plus
+ * `len(paths) × len(directories)` join calls per resolve.
+ *
+ * The outer map is keyed on the directories array reference (plugin-owned,
+ * stable for the lifetime of the resolver), and the inner map on the
+ * starting `request.path`. Kept private to this module (rather than hung off
+ * `resolver.pathCache`) so the pathCache's hidden-class shape is unchanged —
+ * that avoids perturbing the interpreter-mode IC state for the
+ * `resolver.pathCache.{join,dirname,basename}.fn(...)` accesses that run on
+ * every resolve, which the CodSpeed instruction-count harness is sensitive to.
+ * @type {WeakMap<string[], Map<string, string[]>>}
+ */
+const _addrsCacheByDirs = new WeakMap();
+
+/**
+ * @param {Resolver} resolver resolver
+ * @param {string[]} directories directories
+ * @param {ResolveStepHook} target target
+ * @param {ResolveRequest} request request
+ * @param {ResolveContext} resolveContext resolve context
+ * @param {InnerCallback} callback callback
+ * @returns {void}
+ */
+function modulesResolveHandler(
+	resolver,
+	directories,
+	target,
+	request,
+	resolveContext,
+	callback,
+) {
+	const fs = resolver.fileSystem;
+	const requestPath = /** @type {string} */ (request.path);
+	// Compute-or-reuse the flat `addrs` list. Inlined (rather than a helper
+	// function) so the cache-hit path — which is the vast majority of
+	// invocations — stays a single WeakMap + Map lookup with no function-call
+	// overhead. See `_addrsCacheByDirs` above for caching rationale.
+	let addrs;
+	let perPath = _addrsCacheByDirs.get(directories);
+	if (perPath === undefined) {
+		perPath = new Map();
+		_addrsCacheByDirs.set(directories, perPath);
+	} else {
+		addrs = perPath.get(requestPath);
+	}
+	if (addrs === undefined) {
+		const { paths } = getPathsCached(fs, requestPath);
+		const pathsLen = paths.length;
+		const dirsLen = directories.length;
+		// Pre-size the flat array rather than going through `map().reduce()`
+		// with intermediate arrays + spreads.
+		// eslint-disable-next-line unicorn/no-new-array
+		addrs = new Array(pathsLen * dirsLen);
+		let idx = 0;
+		const joinFn = resolver.pathCache.join.fn;
+		for (let pi = 0; pi < pathsLen; pi++) {
+			const pathItem = paths[pi];
+			for (let di = 0; di < dirsLen; di++) {
+				addrs[idx++] = joinFn(pathItem, directories[di]);
+			}
+		}
+		perPath.set(requestPath, addrs);
+	}
+	// Hoist the dot-prefixed request out of the per-addr iterator. `addrs`
+	// can have up to `paths.length × directories.length` entries (e.g. 36
+	// for an 8-deep source dir × 4-module config), and concatenating the
+	// same `./${request.request}` string on every iteration is wasted
+	// work — it's constant for the whole fan-out.
+	const relRequest = `./${request.request}`;
+	forEachBail(
+		addrs,
+		/**
+		 * @param {string} addr addr
+		 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+		 * @returns {void}
+		 */
+		(addr, callback) => {
+			fs.stat(addr, (err, stat) => {
+				if (!err && stat && stat.isDirectory()) {
+					/** @type {ResolveRequest} */
+					const obj = {
+						...request,
+						path: addr,
+						request: relRequest,
+						module: false,
+					};
+					const message = `looking for modules in ${addr}`;
+					return resolver.doResolve(
+						target,
+						obj,
+						message,
+						resolveContext,
+						callback,
+					);
+				}
+				if (resolveContext.log) {
+					resolveContext.log(`${addr} doesn't exist or is not a directory`);
+				}
+				if (resolveContext.missingDependencies) {
+					resolveContext.missingDependencies.add(addr);
+				}
+				return callback();
+			});
+		},
+		callback,
+	);
+}
+
+module.exports = {
+	modulesResolveHandler,
+};
Index: frontend/node_modules/enhanced-resolve/lib/NextPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/NextPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/NextPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,33 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class NextPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("NextPlugin", (request, resolveContext, callback) => {
+				resolver.doResolve(target, request, null, resolveContext, callback);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ParsePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ParsePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ParsePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,77 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class ParsePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {Partial<ResolveRequest>} requestOptions request options
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, requestOptions, target) {
+		this.source = source;
+		this.requestOptions = requestOptions;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("ParsePlugin", (request, resolveContext, callback) => {
+				const parsed = resolver.parse(/** @type {string} */ (request.request));
+				/** @type {ResolveRequest} */
+				const obj = { ...request, ...parsed, ...this.requestOptions };
+				if (request.query && !parsed.query) {
+					obj.query = request.query;
+				}
+				if (request.fragment && !parsed.fragment) {
+					obj.fragment = request.fragment;
+				}
+				if (parsed && resolveContext.log) {
+					if (parsed.module) resolveContext.log("Parsed request is a module");
+					if (parsed.directory) {
+						resolveContext.log("Parsed request is a directory");
+					}
+				}
+				// There is an edge-case where a request with # can be a path or a fragment -> try both
+				if (obj.request && !obj.query && obj.fragment) {
+					const directory = obj.fragment.endsWith("/");
+					/** @type {ResolveRequest} */
+					const alternative = {
+						...obj,
+						directory,
+						request:
+							obj.request +
+							(obj.directory ? "/" : "") +
+							(directory ? obj.fragment.slice(0, -1) : obj.fragment),
+						fragment: "",
+					};
+					resolver.doResolve(
+						target,
+						alternative,
+						null,
+						resolveContext,
+						(err, result) => {
+							if (err) return callback(err);
+							if (result) return callback(null, result);
+							resolver.doResolve(target, obj, null, resolveContext, callback);
+						},
+					);
+					return;
+				}
+				resolver.doResolve(target, obj, null, resolveContext, callback);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/PnpPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/PnpPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/PnpPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,134 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Maël Nison @arcanis
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/**
+ * @typedef {object} PnpApiImpl
+ * @property {(packageName: string, issuer: string, options: { considerBuiltins: boolean }) => string | null} resolveToUnqualified resolve to unqualified
+ */
+
+module.exports = class PnpPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {PnpApiImpl} pnpApi pnpApi
+	 * @param {string | ResolveStepHook} target target
+	 * @param {string | ResolveStepHook} alternateTarget alternateTarget
+	 */
+	constructor(source, pnpApi, target, alternateTarget) {
+		this.source = source;
+		this.pnpApi = pnpApi;
+		this.target = target;
+		this.alternateTarget = alternateTarget;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		/** @type {ResolveStepHook} */
+		const target = resolver.ensureHook(this.target);
+		const alternateTarget = resolver.ensureHook(this.alternateTarget);
+		resolver
+			.getHook(this.source)
+			.tapAsync("PnpPlugin", (request, resolveContext, callback) => {
+				const req = request.request;
+				if (!req) return callback();
+
+				// The trailing slash indicates to PnP that this value is a folder rather than a file
+				const issuer = `${request.path}/`;
+
+				const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req);
+				if (!packageMatch) return callback();
+
+				const [packageName] = packageMatch;
+				const innerRequest = `.${req.slice(packageName.length)}`;
+
+				/** @type {string | undefined | null} */
+				let resolution;
+				/** @type {string | undefined | null} */
+				let apiResolution;
+				try {
+					resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
+						considerBuiltins: false,
+					});
+
+					if (resolution === null) {
+						// This is either not a PnP managed issuer or it's a Node builtin
+						// Try to continue resolving with our alternatives
+						resolver.doResolve(
+							alternateTarget,
+							request,
+							"issuer is not managed by a pnpapi",
+							resolveContext,
+							(err, result) => {
+								if (err) return callback(err);
+								if (result) return callback(null, result);
+								// Skip alternatives
+								return callback(null, null);
+							},
+						);
+						return;
+					}
+
+					if (resolveContext.fileDependencies) {
+						apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
+							considerBuiltins: false,
+						});
+					}
+				} catch (/** @type {unknown} */ error) {
+					if (
+						/** @type {Error & { code: string }} */
+						(error).code === "MODULE_NOT_FOUND" &&
+						/** @type {Error & { pnpCode: string }} */
+						(error).pnpCode === "UNDECLARED_DEPENDENCY"
+					) {
+						// This is not a PnP managed dependency.
+						// Try to continue resolving with our alternatives
+						if (resolveContext.log) {
+							resolveContext.log("request is not managed by the pnpapi");
+							for (const line of /** @type {Error} */ (error).message
+								.split("\n")
+								.filter(Boolean)) {
+								resolveContext.log(`  ${line}`);
+							}
+						}
+						return callback();
+					}
+					return callback(/** @type {Error} */ (error));
+				}
+
+				if (resolution === packageName) return callback();
+
+				if (apiResolution && resolveContext.fileDependencies) {
+					resolveContext.fileDependencies.add(apiResolution);
+				}
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: resolution,
+					request: innerRequest,
+					ignoreSymlinks: true,
+					fullySpecified: request.fullySpecified && innerRequest !== ".",
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					`resolved by pnp to ${resolution}`,
+					resolveContext,
+					(err, result) => {
+						if (err) return callback(err);
+						if (result) return callback(null, result);
+						// Skip alternatives
+						return callback(null, null);
+					},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/Resolver.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/Resolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/Resolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1226 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");
+const createInnerContext = require("./createInnerContext");
+const { parseIdentifier } = require("./util/identifier");
+const {
+	PathType,
+	createCachedBasename,
+	createCachedDirname,
+	createCachedJoin,
+	getType,
+	normalize,
+} = require("./util/path");
+
+/* eslint-disable jsdoc/check-alignment */
+// TODO in the next major release use only `Promise.withResolvers()`
+const _withResolvers =
+	// eslint-disable-next-line n/no-unsupported-features/es-syntax
+	Promise.withResolvers
+		? /**
+			 * @param {Resolver} self resolver
+			 * @param {Context} context context information object
+			 * @param {string} path context path
+			 * @param {string} request request string
+			 * @param {ResolveContext} resolveContext resolve context
+			 * @returns {Promise<string | false>} result
+			 */
+			(self, context, path, request, resolveContext) => {
+				// eslint-disable-next-line n/no-unsupported-features/es-syntax
+				const { promise, resolve, reject } = Promise.withResolvers();
+				self.resolve(context, path, request, resolveContext, (err, res) => {
+					if (err) reject(err);
+					else resolve(/** @type {string | false} */ (res));
+				});
+				return promise;
+			}
+		: /**
+			 * @param {Resolver} self resolver
+			 * @param {Context} context context information object
+			 * @param {string} path context path
+			 * @param {string} request request string
+			 * @param {ResolveContext} resolveContext resolve context
+			 * @returns {Promise<string | false>} result
+			 */
+			(self, context, path, request, resolveContext) =>
+				new Promise((resolve, reject) => {
+					self.resolve(context, path, request, resolveContext, (err, res) => {
+						if (err) reject(err);
+						else resolve(/** @type {string | false} */ (res));
+					});
+				});
+/* eslint-enable jsdoc/check-alignment */
+
+/** @typedef {import("./AliasUtils").AliasOption} AliasOption */
+/** @typedef {import("./util/path").CachedJoin} CachedJoin */
+/** @typedef {import("./util/path").CachedDirname} CachedDirname */
+/** @typedef {import("./util/path").CachedBasename} CachedBasename */
+
+/**
+ * @typedef {object} JoinCacheEntry
+ * @property {CachedJoin["fn"]} fn cached join function
+ * @property {CachedJoin["cache"]} cache the underlying cache map
+ */
+
+/**
+ * @typedef {object} DirnameCacheEntry
+ * @property {CachedDirname["fn"]} fn cached dirname function
+ * @property {CachedDirname["cache"]} cache the underlying cache map
+ */
+
+/**
+ * @typedef {object} BasenameCacheEntry
+ * @property {CachedBasename["fn"]} fn cached dirname function
+ * @property {CachedBasename["cache"]} cache the underlying cache map
+ */
+
+/**
+ * @typedef {object} PathCacheFunctions
+ * @property {JoinCacheEntry} join cached join
+ * @property {DirnameCacheEntry} dirname cached dirname
+ * @property {BasenameCacheEntry} basename cached basename
+ */
+
+/** @type {WeakMap<FileSystem, PathCacheFunctions>} */
+const _pathCacheByFs = new WeakMap();
+
+const HASH_ESCAPE_RE = /#/g;
+
+/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
+
+/**
+ * @typedef {object} KnownContext
+ * @property {string[]=} environments environments
+ */
+
+// eslint-disable-next-line jsdoc/reject-any-type
+/** @typedef {KnownContext & Record<any, any>} Context */
+
+/** @typedef {Error & { details?: string }} ErrorWithDetail */
+
+/** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback */
+
+/**
+ * @typedef {object} PossibleFileSystemError
+ * @property {string=} code code
+ * @property {number=} errno number
+ * @property {string=} path path
+ * @property {string=} syscall syscall
+ */
+
+/**
+ * @template T
+ * @callback FileSystemCallback
+ * @param {PossibleFileSystemError & Error | null} err
+ * @param {T=} result
+ */
+
+/**
+ * @typedef {string | Buffer | URL} PathLike
+ */
+
+/**
+ * @typedef {PathLike | number} PathOrFileDescriptor
+ */
+
+/**
+ * @typedef {object} ObjectEncodingOptions
+ * @property {BufferEncoding | null | undefined=} encoding encoding
+ */
+
+/**
+ * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption
+ */
+
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: string) => void} StringCallback */
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: Buffer) => void} BufferCallback */
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: (string | Buffer)) => void} StringOrBufferCallback */
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: IStats) => void} StatsCallback */
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: IBigIntStats) => void} BigIntStatsCallback */
+/** @typedef {(err: NodeJS.ErrnoException | null, result?: (IStats | IBigIntStats)) => void} StatsOrBigIntStatsCallback */
+/** @typedef {(err: NodeJS.ErrnoException | Error | null, result?: JsonObject) => void} ReadJsonCallback */
+
+/**
+ * @template T
+ * @typedef {object} IStatsBase
+ * @property {() => boolean} isFile is file
+ * @property {() => boolean} isDirectory is directory
+ * @property {() => boolean} isBlockDevice is block device
+ * @property {() => boolean} isCharacterDevice is character device
+ * @property {() => boolean} isSymbolicLink is symbolic link
+ * @property {() => boolean} isFIFO is FIFO
+ * @property {() => boolean} isSocket is socket
+ * @property {T} dev dev
+ * @property {T} ino ino
+ * @property {T} mode mode
+ * @property {T} nlink nlink
+ * @property {T} uid uid
+ * @property {T} gid gid
+ * @property {T} rdev rdev
+ * @property {T} size size
+ * @property {T} blksize blksize
+ * @property {T} blocks blocks
+ * @property {T} atimeMs atime ms
+ * @property {T} mtimeMs mtime ms
+ * @property {T} ctimeMs ctime ms
+ * @property {T} birthtimeMs birthtime ms
+ * @property {Date} atime atime
+ * @property {Date} mtime mtime
+ * @property {Date} ctime ctime
+ * @property {Date} birthtime birthtime
+ */
+
+/**
+ * @typedef {IStatsBase<number>} IStats
+ */
+
+/**
+ * @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats
+ */
+
+/**
+ * @template {string | Buffer} [T=string]
+ * @typedef {object} Dirent
+ * @property {() => boolean} isFile true when is file, otherwise false
+ * @property {() => boolean} isDirectory true when is directory, otherwise false
+ * @property {() => boolean} isBlockDevice true when is block device, otherwise false
+ * @property {() => boolean} isCharacterDevice true when is character device, otherwise false
+ * @property {() => boolean} isSymbolicLink true when is symbolic link, otherwise false
+ * @property {() => boolean} isFIFO true when is FIFO, otherwise false
+ * @property {() => boolean} isSocket true when is socket, otherwise false
+ * @property {T} name name
+ * @property {string} parentPath path
+ * @property {string=} path path
+ */
+
+/**
+ * @typedef {object} StatOptions
+ * @property {(boolean | undefined)=} bigint need bigint values
+ */
+
+/**
+ * @typedef {object} StatSyncOptions
+ * @property {(boolean | undefined)=} bigint need bigint values
+ * @property {(boolean | undefined)=} throwIfNoEntry throw if no entry
+ */
+
+/**
+ * @typedef {{
+ * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void,
+ * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void,
+ * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void,
+ * (path: PathOrFileDescriptor, callback: BufferCallback): void,
+ * }} ReadFile
+ */
+
+/**
+ * @typedef {"buffer" | { encoding: "buffer" }} BufferEncodingOption
+ */
+
+/**
+ * @typedef {{
+ * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer,
+ * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string,
+ * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer,
+ * }} ReadFileSync
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
+ * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer", callback: (err: NodeJS.ErrnoException | null, files?: Buffer[]) => void): void,
+ * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[] | Buffer[]) => void): void,
+ * (path: PathLike, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
+ * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files?: Dirent<string>[]) => void): void,
+ * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files: Dirent<Buffer>[]) => void): void,
+ * }} Readdir
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[],
+ * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer"): Buffer[],
+ * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[],
+ * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[],
+ * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }): Dirent<Buffer>[],
+ * }} ReaddirSync
+ */
+
+/**
+ * @typedef {(pathOrFileDescription: PathOrFileDescriptor, callback: ReadJsonCallback) => void} ReadJson
+ */
+
+/**
+ * @typedef {(pathOrFileDescription: PathOrFileDescriptor) => JsonObject} ReadJsonSync
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
+ * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
+ * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
+ * (path: PathLike, callback: StringCallback): void,
+ * }} Readlink
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options?: EncodingOption): string,
+ * (path: PathLike, options: BufferEncodingOption): Buffer,
+ * (path: PathLike, options?: EncodingOption): string | Buffer,
+ * }} ReadlinkSync
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, callback: StatsCallback): void,
+ * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
+ * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
+ * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
+ * }} LStat
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options?: undefined): IStats,
+ * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
+ * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
+ * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
+ * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
+ * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
+ * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
+ * }} LStatSync
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, callback: StatsCallback): void,
+ * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
+ * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
+ * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
+ * }} Stat
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options?: undefined): IStats,
+ * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
+ * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
+ * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
+ * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
+ * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
+ * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
+ * }} StatSync
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
+ * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
+ * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
+ * (path: PathLike, callback: StringCallback): void,
+ * }} RealPath
+ */
+
+/**
+ * @typedef {{
+ * (path: PathLike, options?: EncodingOption): string,
+ * (path: PathLike, options: BufferEncodingOption): Buffer,
+ * (path: PathLike, options?: EncodingOption): string | Buffer,
+ * }} RealPathSync
+ */
+
+/**
+ * @typedef {object} FileSystem
+ * @property {ReadFile} readFile read file method
+ * @property {Readdir} readdir readdir method
+ * @property {ReadJson=} readJson read json method
+ * @property {Readlink} readlink read link method
+ * @property {LStat=} lstat lstat method
+ * @property {Stat} stat stat method
+ * @property {RealPath=} realpath realpath method
+ */
+
+/**
+ * @typedef {object} SyncFileSystem
+ * @property {ReadFileSync} readFileSync read file sync method
+ * @property {ReaddirSync} readdirSync read dir sync method
+ * @property {ReadJsonSync=} readJsonSync read json sync method
+ * @property {ReadlinkSync} readlinkSync read link sync method
+ * @property {LStatSync=} lstatSync lstat sync method
+ * @property {StatSync} statSync stat sync method
+ * @property {RealPathSync=} realpathSync real path sync method
+ */
+
+/**
+ * @typedef {object} ParsedIdentifier
+ * @property {string} request request
+ * @property {string} query query
+ * @property {string} fragment fragment
+ * @property {boolean} directory is directory
+ * @property {boolean} module is module
+ * @property {boolean} file is file
+ * @property {boolean} internal is internal
+ */
+
+/** @typedef {string | number | boolean | null} JsonPrimitive */
+/** @typedef {JsonValue[]} JsonArray */
+/** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */
+/** @typedef {{ [Key in string]?: JsonValue | undefined }} JsonObject */
+
+/**
+ * @typedef {object} TsconfigPathsMap
+ * @property {TsconfigPathsData} main main tsconfig paths data
+ * @property {string} mainContext main tsconfig base URL (absolute path)
+ * @property {{ [baseUrl: string]: TsconfigPathsData }} refs referenced tsconfig paths data mapped by baseUrl
+ * @property {{ [context: string]: TsconfigPathsData }} allContexts all contexts (main + refs) for quick lookup
+ * @property {string[]} contextList precomputed `Object.keys(allContexts)` — read-only; used on the `_selectPathsDataForContext` hot path
+ * @property {Set<string>} fileDependencies file dependencies
+ */
+
+/**
+ * @typedef {object} TsconfigPathsData
+ * @property {import("./AliasUtils").CompiledAliasOptions} alias tsconfig file data
+ * @property {string[]} modules tsconfig file data
+ */
+
+/**
+ * @typedef {object} BaseResolveRequest
+ * @property {string | false} path path
+ * @property {Context=} context content
+ * @property {string=} descriptionFilePath description file path
+ * @property {string=} descriptionFileRoot description file root
+ * @property {JsonObject=} descriptionFileData description file data
+ * @property {TsconfigPathsMap | null | undefined=} tsconfigPathsMap tsconfig paths map
+ * @property {string=} relativePath relative path
+ * @property {boolean=} ignoreSymlinks true when need to ignore symlinks, otherwise false
+ * @property {boolean=} fullySpecified true when full specified, otherwise false
+ * @property {string=} __innerRequest inner request for internal usage
+ * @property {string=} __innerRequest_request inner request for internal usage
+ * @property {string=} __innerRequest_relativePath inner relative path for internal usage
+ */
+
+/** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
+
+/**
+ * @template T
+ * @typedef {{ add: (item: T) => void }} WriteOnlySet
+ */
+
+/** @typedef {(request: ResolveRequest) => void} ResolveContextYield */
+
+/**
+ * Singly-linked stack entry that also exposes a Set-like API
+ * (`has`, `size`, iteration). Each `doResolve` call prepends a new
+ * `StackEntry` that points at the previous tip via `.parent`, so pushing
+ * is O(1) in time and memory. Recursion detection walks the linked list
+ * (O(n)) but the stack is typically shallow, so this is cheaper overall
+ * than cloning a `Set` per call.
+ */
+class StackEntry {
+	/**
+	 * @param {ResolveStepHook} hook hook
+	 * @param {ResolveRequest} request request
+	 * @param {StackEntry=} parent previous tip
+	 * @param {Set<string>=} preSeeded entries pre-seeded via the legacy `Set<string>` API
+	 */
+	constructor(hook, request, parent, preSeeded) {
+		this.name = hook.name;
+		this.path = request.path;
+		this.request = request.request || "";
+		this.query = request.query || "";
+		this.fragment = request.fragment || "";
+		this.directory = Boolean(request.directory);
+		this.module = Boolean(request.module);
+		/** @type {StackEntry | undefined} */
+		this.parent = parent;
+		/**
+		 * Strings seeded by callers that still pass `stack: new Set([...])`.
+		 * Propagated through the chain so deeper `doResolve` calls still see
+		 * them during recursion checks. `undefined` in the common case so
+		 * there is no extra work on the hot path.
+		 * @type {Set<string> | undefined}
+		 */
+		this.preSeeded = preSeeded;
+	}
+
+	/**
+	 * 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
+	 */
+	has(query) {
+		/** @type {StackEntry | undefined} */
+		let node = this;
+		while (node) {
+			if (
+				node.name === query.name &&
+				node.path === query.path &&
+				node.request === query.request &&
+				node.query === query.query &&
+				node.fragment === query.fragment &&
+				node.directory === query.directory &&
+				node.module === query.module
+			) {
+				return true;
+			}
+			node = node.parent;
+		}
+		return this.preSeeded !== undefined && this.preSeeded.has(query.toString());
+	}
+
+	/**
+	 * Number of entries on the stack (oldest-to-newest length).
+	 * @returns {number} size
+	 */
+	get size() {
+		let count = this.preSeeded ? this.preSeeded.size : 0;
+		/** @type {StackEntry | undefined} */
+		let node = this;
+		while (node) {
+			count++;
+			node = node.parent;
+		}
+		return count;
+	}
+
+	/**
+	 * Iterate entries from oldest (root) to newest (tip), matching how a
+	 * `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.
+	 *
+	 * 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) {
+			for (const entry of this.preSeeded) yield entry;
+		}
+		/** @type {StackEntry[]} */
+		const entries = [];
+		/** @type {StackEntry | undefined} */
+		let node = this;
+		while (node) {
+			entries.push(node);
+			node = node.parent;
+		}
+		for (let i = entries.length - 1; i >= 0; i--) yield entries[i].toString();
+	}
+
+	/**
+	 * 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() {
+		return `${this.name}: (${this.path}) ${this.request}${this.query}${
+			this.fragment
+		}${this.directory ? " directory" : ""}${this.module ? " module" : ""}`;
+	}
+}
+
+/**
+ * Resolve context
+ * @typedef {object} ResolveContext
+ * @property {WriteOnlySet<string>=} contextDependencies directories that was found on file system
+ * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system
+ * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
+ * @property {StackEntry | Set<string>=} stack tip of the resolver call stack (a singly-linked list with Set-like API). For instance, `resolve → parsedResolve → describedResolve`. Accepts a legacy `Set<string>` for back-compat with older callers; it is normalized internally without a hot-path branch.
+ * @property {((str: string) => void)=} log log function
+ * @property {ResolveContextYield=} yield yield result, if provided plugins can return several results
+ */
+
+/** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
+
+/**
+ * @typedef {object} KnownHooks
+ * @property {SyncHook<[ResolveStepHook, ResolveRequest], void>} resolveStep resolve step hook
+ * @property {SyncHook<[ResolveRequest, Error]>} noResolve no resolve hook
+ * @property {ResolveStepHook} resolve resolve hook
+ * @property {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} result result hook
+ */
+
+/**
+ * @typedef {{ [key: string]: ResolveStepHook }} EnsuredHooks
+ */
+
+/**
+ * @param {string} str input string
+ * @returns {string} in camel case
+ */
+function toCamelCase(str) {
+	return str.replace(/-([a-z])/g, (str) => str.slice(1).toUpperCase());
+}
+
+class Resolver {
+	/**
+	 * @param {ResolveStepHook} hook hook
+	 * @param {ResolveRequest} request request
+	 * @param {StackEntry=} parent previous tip of the stack
+	 * @param {Set<string>=} preSeeded entries pre-seeded via the legacy `Set<string>` API
+	 * @returns {StackEntry} stack entry
+	 */
+	static createStackEntry(hook, request, parent, preSeeded) {
+		return new StackEntry(hook, request, parent, preSeeded);
+	}
+
+	/**
+	 * @param {FileSystem} fileSystem a filesystem
+	 * @param {ResolveOptions} options options
+	 */
+	constructor(fileSystem, options) {
+		/** @type {FileSystem} */
+		this.fileSystem = fileSystem;
+		/** @type {ResolveOptions} */
+		this.options = options;
+		let pathCache = _pathCacheByFs.get(fileSystem);
+		if (!pathCache) {
+			pathCache = {
+				join: createCachedJoin(),
+				dirname: createCachedDirname(),
+				basename: createCachedBasename(),
+			};
+			_pathCacheByFs.set(fileSystem, pathCache);
+		}
+		/** @type {PathCacheFunctions} */
+		this.pathCache = pathCache;
+		/** @type {KnownHooks} */
+		this.hooks = {
+			resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
+			noResolve: new SyncHook(["request", "error"], "noResolve"),
+			resolve: new AsyncSeriesBailHook(
+				["request", "resolveContext"],
+				"resolve",
+			),
+			result: new AsyncSeriesHook(["result", "resolveContext"], "result"),
+		};
+	}
+
+	/**
+	 * @param {string | ResolveStepHook} name hook name or hook itself
+	 * @returns {ResolveStepHook} the hook
+	 */
+	ensureHook(name) {
+		if (typeof name !== "string") {
+			return name;
+		}
+		name = toCamelCase(name);
+		if (name.startsWith("before")) {
+			return /** @type {ResolveStepHook} */ (
+				this.ensureHook(name[6].toLowerCase() + name.slice(7)).withOptions({
+					stage: -10,
+				})
+			);
+		}
+		if (name.startsWith("after")) {
+			return /** @type {ResolveStepHook} */ (
+				this.ensureHook(name[5].toLowerCase() + name.slice(6)).withOptions({
+					stage: 10,
+				})
+			);
+		}
+		/** @type {ResolveStepHook} */
+		const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
+		if (!hook) {
+			/** @type {KnownHooks & EnsuredHooks} */
+			(this.hooks)[name] = new AsyncSeriesBailHook(
+				["request", "resolveContext"],
+				name,
+			);
+
+			return /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
+		}
+		return hook;
+	}
+
+	/**
+	 * @param {string | ResolveStepHook} name hook name or hook itself
+	 * @returns {ResolveStepHook} the hook
+	 */
+	getHook(name) {
+		if (typeof name !== "string") {
+			return name;
+		}
+		name = toCamelCase(name);
+		if (name.startsWith("before")) {
+			return /** @type {ResolveStepHook} */ (
+				this.getHook(name[6].toLowerCase() + name.slice(7)).withOptions({
+					stage: -10,
+				})
+			);
+		}
+		if (name.startsWith("after")) {
+			return /** @type {ResolveStepHook} */ (
+				this.getHook(name[5].toLowerCase() + name.slice(6)).withOptions({
+					stage: 10,
+				})
+			);
+		}
+		/** @type {ResolveStepHook} */
+		const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
+		if (!hook) {
+			throw new Error(`Hook ${name} doesn't exist`);
+		}
+		return hook;
+	}
+
+	/**
+	 * @overload
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {string | false} result
+	 */
+	/**
+	 * @overload
+	 * @param {Context} context context information object
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {string | false} result
+	 */
+	/**
+	 * @param {Context | string} context context information object or context path when no context is provided
+	 * @param {string | ResolveContext=} path context path or resolve context when no context is provided
+	 * @param {string | ResolveContext=} request request string or resolve context when no context is provided
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {string | false} result
+	 */
+	resolveSync(context, path, request, resolveContext) {
+		/** @type {Error | null | undefined} */
+		let err;
+		/** @type {string | false | undefined} */
+		let result;
+		let sync = false;
+		// `|| {}` so the underlying `resolve()` hits its 5-arg fast path
+		// (skips the overload-shifting prologue) regardless of whether the
+		// caller supplied a resolveContext.
+		this.resolve(
+			/** @type {Context} */ (context),
+			/** @type {string} */ (path),
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext) || {},
+			(_err, r) => {
+				err = _err;
+				result = r;
+				sync = true;
+			},
+		);
+		if (!sync) {
+			throw new Error(
+				"Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!",
+			);
+		}
+		if (err) throw err;
+		if (result === undefined) throw new Error("No result");
+		return result;
+	}
+
+	/**
+	 * @overload
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {Promise<string | false>} result
+	 */
+	/**
+	 * @overload
+	 * @param {Context} context context information object
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {Promise<string | false>} result
+	 */
+	/**
+	 * @param {Context | string} context context information object or context path when no context is provided
+	 * @param {string | ResolveContext=} path context path or resolve context when no context is provided
+	 * @param {string | ResolveContext=} request request string or resolve context when no context is provided
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {Promise<string | false>} result
+	 */
+	resolvePromise(context, path, request, resolveContext) {
+		// `|| {}` ensures the 5-arg fast path inside `resolve()` is reached
+		// even when the caller doesn't pass a resolveContext.
+		return _withResolvers(
+			this,
+			/** @type {Context} */ (context),
+			/** @type {string} */ (path),
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext) || {},
+		);
+	}
+
+	/**
+	 * @overload
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveCallback} callback callback function
+	 * @returns {void}
+	 */
+	/**
+	 * @overload
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext} resolveContext resolve context
+	 * @param {ResolveCallback} callback callback function
+	 * @returns {void}
+	 */
+	/**
+	 * @overload
+	 * @param {Context} context context information object
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveCallback} callback callback function
+	 * @returns {void}
+	 */
+	/**
+	 * @overload
+	 * @param {Context} context context information object
+	 * @param {string} path context path
+	 * @param {string} request request string
+	 * @param {ResolveContext} resolveContext resolve context
+	 * @param {ResolveCallback} callback callback function
+	 * @returns {void}
+	 */
+	/**
+	 * @param {Context | string} context context information object or context path when no context is provided
+	 * @param {string | ResolveContext | ResolveCallback=} path context path or (when no context) resolve context or callback
+	 * @param {string | ResolveContext | ResolveCallback=} request request string or (when no context) resolve context or callback
+	 * @param {ResolveContext | ResolveCallback=} resolveContext resolve context or callback when no resolve context is provided
+	 * @param {ResolveCallback=} callback callback function
+	 * @returns {void}
+	 */
+	resolve(context, path, request, resolveContext, callback) {
+		// Fast path for the common 5-arg call (`resolver.resolve(ctx, from,
+		// req, resolveCtx, cb)`) — every call from `resolveSync` /
+		// `resolvePromise` plus the vast majority of direct API callers.
+		// PR #536 added runtime overload-shifting to support optional
+		// `context` / `resolveContext`; that adds several `typeof` checks
+		// per resolve which show up as a measurable instruction-count
+		// regression on every benchmark that calls into this method. Skip
+		// the shifting entirely when all 5 args are already well-typed.
+		if (
+			typeof callback === "function" &&
+			typeof context === "object" &&
+			context !== null &&
+			typeof resolveContext === "object" &&
+			resolveContext !== null
+		) {
+			// proceed straight to per-arg validation below
+		} else {
+			// Slow path: shift positional args based on what was supplied.
+			// Shift when context is omitted (first positional arg is the path string).
+			if (typeof context === "string") {
+				// Keep an already-supplied callback (resolveSync / resolvePromise
+				// always pass one in the 5th position).
+				if (typeof callback !== "function") {
+					callback = /** @type {ResolveCallback | undefined} */ (
+						resolveContext
+					);
+				}
+				resolveContext =
+					/** @type {ResolveContext | ResolveCallback | undefined} */ (request);
+				request = /** @type {string} */ (path);
+				path = context;
+				context = {};
+			}
+			// 4-arg form: the resolveContext slot holds the callback.
+			if (typeof resolveContext === "function") {
+				callback = resolveContext;
+				resolveContext = {};
+			} else if (!resolveContext || typeof resolveContext !== "object") {
+				resolveContext = {};
+			}
+			if (typeof callback !== "function") {
+				throw new TypeError("callback argument is not a function");
+			}
+			if (!context || typeof context !== "object") {
+				context = {};
+			}
+		}
+		if (typeof path !== "string") {
+			return callback(new Error("path argument is not a string"));
+		}
+		if (typeof request !== "string") {
+			return callback(new Error("request argument is not a string"));
+		}
+
+		/** @type {ResolveRequest} */
+		const obj = {
+			context,
+			path,
+			request,
+		};
+
+		/** @type {ResolveContextYield | undefined} */
+		let yield_;
+		let yieldCalled = false;
+		/** @type {ResolveContextYield | undefined} */
+		let finishYield;
+		if (typeof resolveContext.yield === "function") {
+			const old = resolveContext.yield;
+			/**
+			 * @param {ResolveRequest} obj object
+			 */
+			yield_ = (obj) => {
+				old(obj);
+				yieldCalled = true;
+			};
+			/**
+			 * @param {ResolveRequest} result result
+			 * @returns {void}
+			 */
+			finishYield = (result) => {
+				if (result) {
+					/** @type {ResolveContextYield} */ (yield_)(result);
+				}
+				callback(null);
+			};
+		}
+
+		const message = `resolve '${request}' in '${path}'`;
+
+		/**
+		 * @param {ResolveRequest} result result
+		 * @returns {void}
+		 */
+		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,
+				`${escapedPath}${escapedQuery}${result.fragment || ""}`,
+				result,
+			);
+		};
+
+		/**
+		 * @param {string[]} log logs
+		 * @returns {void}
+		 */
+		const finishWithoutResolve = (log) => {
+			/**
+			 * @type {ErrorWithDetail}
+			 */
+			const error = new Error(`Can't ${message}`);
+			error.details = log.join("\n");
+			this.hooks.noResolve.call(obj, error);
+			return callback(error);
+		};
+
+		if (resolveContext.log) {
+			// We need log anyway to capture it in case of an error
+			const parentLog = resolveContext.log;
+			/** @type {string[]} */
+			const log = [];
+			return this.doResolve(
+				this.hooks.resolve,
+				obj,
+				message,
+				{
+					log: (msg) => {
+						parentLog(msg);
+						log.push(msg);
+					},
+					yield: yield_,
+					fileDependencies: resolveContext.fileDependencies,
+					contextDependencies: resolveContext.contextDependencies,
+					missingDependencies: resolveContext.missingDependencies,
+					stack: resolveContext.stack,
+				},
+				(err, result) => {
+					if (err) return callback(err);
+
+					if (yieldCalled || (result && yield_)) {
+						return /** @type {ResolveContextYield} */ (finishYield)(
+							/** @type {ResolveRequest} */ (result),
+						);
+					}
+
+					if (result) return finishResolved(result);
+
+					return finishWithoutResolve(log);
+				},
+			);
+		}
+		// Try to resolve assuming there is no error
+		// We don't log stuff in this case
+		return this.doResolve(
+			this.hooks.resolve,
+			obj,
+			message,
+			{
+				log: undefined,
+				yield: yield_,
+				fileDependencies: resolveContext.fileDependencies,
+				contextDependencies: resolveContext.contextDependencies,
+				missingDependencies: resolveContext.missingDependencies,
+				stack: resolveContext.stack,
+			},
+			(err, result) => {
+				if (err) return callback(err);
+
+				if (yieldCalled || (result && yield_)) {
+					return /** @type {ResolveContextYield} */ (finishYield)(
+						/** @type {ResolveRequest} */ (result),
+					);
+				}
+
+				if (result) return finishResolved(result);
+
+				// log is missing for the error details
+				// so we redo the resolving for the log info
+				// this is more expensive to the success case
+				// is assumed by default
+				/** @type {string[]} */
+				const log = [];
+
+				return this.doResolve(
+					this.hooks.resolve,
+					obj,
+					message,
+					{
+						log: (msg) => log.push(msg),
+						yield: yield_,
+						stack: resolveContext.stack,
+					},
+					(err, result) => {
+						if (err) return callback(err);
+
+						// In a case that there is a race condition and yield will be called
+						if (yieldCalled || (result && yield_)) {
+							return /** @type {ResolveContextYield} */ (finishYield)(
+								/** @type {ResolveRequest} */ (result),
+							);
+						}
+
+						return finishWithoutResolve(log);
+					},
+				);
+			},
+		);
+	}
+
+	/**
+	 * @param {ResolveStepHook} hook hook
+	 * @param {ResolveRequest} request request
+	 * @param {null | string} message string
+	 * @param {ResolveContext} resolveContext resolver context
+	 * @param {(err?: null | Error, result?: ResolveRequest) => void} callback callback
+	 * @returns {void}
+	 */
+	doResolve(hook, request, message, resolveContext, callback) {
+		const rawStack = resolveContext.stack;
+		/** @type {StackEntry | undefined} */
+		let parent;
+		/** @type {Set<string> | undefined} */
+		let preSeeded;
+		if (rawStack instanceof StackEntry) {
+			parent = rawStack;
+			preSeeded = rawStack.preSeeded;
+		} else if (rawStack) {
+			// TODO in the next major remove `Set<string>` support in favor of `StackEntry`
+			// Legacy `stack: new Set<string>()` API: don't link the Set into
+			// the parent chain (it would pollute iteration and field-compare
+			// walks). Carry the strings on the StackEntry itself instead so
+			// deeper `doResolve` calls keep seeing pre-seeded entries.
+			preSeeded = /** @type {Set<string>} */ (rawStack);
+		}
+		// Prepend a new linked-list node. O(1) allocation, no Set clone.
+		const stackEntry = Resolver.createStackEntry(
+			hook,
+			request,
+			parent,
+			preSeeded,
+		);
+
+		// When `parent` exists, its `has()` already consults `preSeeded`
+		// (inherited from the same chain), so we only need the direct Set
+		// lookup on the very first `doResolve` call (no parent yet).
+		if (
+			parent !== undefined
+				? parent.has(stackEntry)
+				: preSeeded !== undefined && preSeeded.has(stackEntry.toString())
+		) {
+			/**
+			 * Prevent recursion
+			 * @type {Error & { recursion?: boolean }}
+			 */
+			const recursionError = new Error(
+				`Recursion in resolving\nStack:\n  ${[...stackEntry].join("\n  ")}`,
+			);
+			recursionError.recursion = true;
+			if (resolveContext.log) {
+				resolveContext.log("abort resolving because of recursion");
+			}
+			return callback(recursionError);
+		}
+		this.hooks.resolveStep.call(hook, request);
+
+		if (hook.isUsed()) {
+			// Pass `resolveContext` and the override fields (stack, message)
+			// directly instead of constructing an intermediate options-object
+			// literal — `createInnerContext` reads from the parent and
+			// allocates exactly one inner context per step. See the comment
+			// on `createInnerContext` itself for the allocation rationale.
+			const innerContext = createInnerContext(
+				resolveContext,
+				stackEntry,
+				message,
+			);
+			return hook.callAsync(request, innerContext, (err, result) => {
+				if (err) return callback(err);
+				if (result) return callback(null, result);
+				callback();
+			});
+		}
+		callback();
+	}
+
+	/**
+	 * @param {string} identifier identifier
+	 * @returns {ParsedIdentifier} parsed identifier
+	 */
+	parse(identifier) {
+		/** @type {ParsedIdentifier} */
+		const part = {
+			request: "",
+			query: "",
+			fragment: "",
+			module: false,
+			directory: false,
+			file: false,
+			internal: false,
+		};
+
+		const parsedIdentifier = parseIdentifier(identifier);
+
+		if (!parsedIdentifier) return part;
+
+		[part.request, part.query, part.fragment] = parsedIdentifier;
+
+		if (part.request.length > 0) {
+			// `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);
+			}
+		}
+
+		return part;
+	}
+
+	/**
+	 * @param {string} path path
+	 * @returns {boolean} true, if the path is a module
+	 */
+	isModule(path) {
+		return getType(path) === PathType.Normal;
+	}
+
+	/**
+	 * @param {string} path path
+	 * @returns {boolean} true, if the path is private
+	 */
+	isPrivate(path) {
+		return getType(path) === PathType.Internal;
+	}
+
+	/**
+	 * @param {string} path a path
+	 * @returns {boolean} true, if the path is a directory path
+	 */
+	isDirectory(path) {
+		return path.endsWith("/");
+	}
+
+	/**
+	 * @param {string} path path
+	 * @returns {string} normalized path
+	 */
+	normalize(path) {
+		return normalize(path);
+	}
+
+	/**
+	 * @param {string} path path
+	 * @param {string} request request
+	 * @returns {string} joined path
+	 */
+	join(path, request) {
+		return this.pathCache.join.fn(path, request);
+	}
+
+	/**
+	 * @param {string} path path
+	 * @returns {string} parent directory
+	 */
+	dirname(path) {
+		return this.pathCache.dirname.fn(path);
+	}
+
+	/**
+	 * @param {string} path the path to evaluate
+	 * @param {string=} suffix an extension to remove from the result
+	 * @returns {string} the last portion of a path
+	 */
+	basename(path, suffix) {
+		return this.pathCache.basename.fn(path, suffix);
+	}
+}
+
+module.exports = Resolver;
Index: frontend/node_modules/enhanced-resolve/lib/ResolverFactory.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ResolverFactory.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ResolverFactory.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,790 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+// eslint-disable-next-line n/prefer-global/process
+const { versions } = require("process");
+
+const AliasFieldPlugin = require("./AliasFieldPlugin");
+const AliasPlugin = require("./AliasPlugin");
+const AppendPlugin = require("./AppendPlugin");
+const ConditionalPlugin = require("./ConditionalPlugin");
+const DescriptionFilePlugin = require("./DescriptionFilePlugin");
+const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
+const ExportsFieldPlugin = require("./ExportsFieldPlugin");
+const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
+const FileExistsPlugin = require("./FileExistsPlugin");
+const ImportsFieldPlugin = require("./ImportsFieldPlugin");
+const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
+const JoinRequestPlugin = require("./JoinRequestPlugin");
+const MainFieldPlugin = require("./MainFieldPlugin");
+const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
+const ModulesInRootPlugin = require("./ModulesInRootPlugin");
+const NextPlugin = require("./NextPlugin");
+const ParsePlugin = require("./ParsePlugin");
+const PnpPlugin = require("./PnpPlugin");
+const Resolver = require("./Resolver");
+const RestrictionsPlugin = require("./RestrictionsPlugin");
+const ResultPlugin = require("./ResultPlugin");
+const RootsPlugin = require("./RootsPlugin");
+const SelfReferencePlugin = require("./SelfReferencePlugin");
+const SymlinkPlugin = require("./SymlinkPlugin");
+const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
+const TryNextPlugin = require("./TryNextPlugin");
+const TsconfigPathsPlugin = require("./TsconfigPathsPlugin");
+const UnsafeCachePlugin = require("./UnsafeCachePlugin");
+const UseFilePlugin = require("./UseFilePlugin");
+const { PathType, getType } = require("./util/path");
+
+/** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
+/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
+/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
+/** @typedef {import("./Resolver").EnsuredHooks} EnsuredHooks */
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").KnownHooks} KnownHooks */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
+/** @typedef {import("./UnsafeCachePlugin").Cache} Cache */
+
+/** @typedef {string | string[] | false} AliasOptionNewRequest */
+/** @typedef {{ [k: string]: AliasOptionNewRequest }} AliasOptions */
+/** @typedef {{ [k: string]: string | string[] }} ExtensionAliasOptions */
+/** @typedef {false | 0 | "" | null | undefined} Falsy */
+/** @typedef {{ apply: (resolver: Resolver) => void } | ((this: Resolver, resolver: Resolver) => void) | Falsy} Plugin */
+
+/**
+ * @typedef {object} TsconfigOptions
+ * @property {string=} configFile A relative path to the tsconfig file based on cwd, or an absolute path of tsconfig file
+ * @property {string[] | "auto"=} references References to other tsconfig files. 'auto' inherits from TypeScript config, or an array of relative/absolute paths
+ * @property {string=} baseUrl Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file
+ */
+
+/**
+ * @typedef {object} UserResolveOptions
+ * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
+ * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
+ * @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
+ * @property {boolean=} extensionAliasForExports Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned); when enabled, matches TypeScript's behavior for packages that ship TS sources alongside compiled JS.
+ * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
+ * @property {((predicate: ResolveRequest) => boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
+ * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
+ * @property {string[]=} descriptionFiles A list of description files to read from
+ * @property {string[]=} conditionNames A list of exports field condition names.
+ * @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
+ * @property {(string | string[])[]=} exportsFields A list of exports fields in description files
+ * @property {(string | string[])[]=} importsFields A list of imports fields in description files
+ * @property {string[]=} extensions A list of extensions which should be tried for files
+ * @property {FileSystem} fileSystem The file system which should be used
+ * @property {(Cache | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
+ * @property {boolean=} symlinks Resolve symlinks to their symlinked location
+ * @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
+ * @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
+ * @property {(string | string[] | { name: string | string[], forceRelative: boolean })[]=} mainFields A list of main fields in description files
+ * @property {string[]=} mainFiles A list of main files in directories
+ * @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
+ * @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
+ * @property {string[]=} roots A list of root paths
+ * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
+ * @property {boolean=} resolveToContext Resolve to a context instead of a file
+ * @property {(string | RegExp)[]=} restrictions A list of resolve restrictions
+ * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
+ * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
+ * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
+ * @property {string | boolean | TsconfigOptions=} tsconfig TypeScript config file path or config object with configFile and references
+ */
+
+/**
+ * @typedef {object} ResolveOptions
+ * @property {AliasOptionEntry[]} alias alias
+ * @property {AliasOptionEntry[]} fallback fallback
+ * @property {Set<string | string[]>} aliasFields alias fields
+ * @property {ExtensionAliasOption[]} extensionAlias extension alias
+ * @property {boolean} extensionAliasForExports apply extension alias to exports field targets
+ * @property {(predicate: ResolveRequest) => boolean} cachePredicate cache predicate
+ * @property {boolean} cacheWithContext cache with context
+ * @property {Set<string>} conditionNames A list of exports field condition names.
+ * @property {string[]} descriptionFiles description files
+ * @property {boolean} enforceExtension enforce extension
+ * @property {Set<string | string[]>} exportsFields exports fields
+ * @property {Set<string | string[]>} importsFields imports fields
+ * @property {Set<string>} extensions extensions
+ * @property {FileSystem} fileSystem fileSystem
+ * @property {Cache | false} unsafeCache unsafe cache
+ * @property {boolean} symlinks symlinks
+ * @property {Resolver=} resolver resolver
+ * @property {(string | string[])[]} modules modules
+ * @property {{ name: string[], forceRelative: boolean }[]} mainFields main fields
+ * @property {Set<string>} mainFiles main files
+ * @property {Plugin[]} plugins plugins
+ * @property {PnpApi | null} pnpApi pnp API
+ * @property {Set<string>} roots roots
+ * @property {boolean} fullySpecified fully specified
+ * @property {boolean} resolveToContext resolve to context
+ * @property {Set<string | RegExp>} restrictions restrictions
+ * @property {boolean} preferRelative prefer relative
+ * @property {boolean} preferAbsolute prefer absolute
+ * @property {string | boolean | TsconfigOptions} tsconfig tsconfig file path or config object
+ */
+
+/**
+ * @param {PnpApi | null=} option option
+ * @returns {PnpApi | null} processed option
+ */
+function processPnpApiOption(option) {
+	if (
+		option === undefined &&
+		/** @type {NodeJS.ProcessVersions & { pnp: string }} */ versions.pnp
+	) {
+		const _findPnpApi =
+			/** @type {(issuer: string) => PnpApi | null}} */
+			(
+				// @ts-expect-error maybe nothing
+				require("module").findPnpApi
+			);
+
+		if (_findPnpApi) {
+			return {
+				resolveToUnqualified(request, issuer, opts) {
+					const pnpapi = _findPnpApi(issuer);
+
+					if (!pnpapi) {
+						// Issuer isn't managed by PnP
+						return null;
+					}
+
+					return pnpapi.resolveToUnqualified(request, issuer, opts);
+				},
+			};
+		}
+	}
+
+	return option || null;
+}
+
+/**
+ * @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
+ * @returns {AliasOptionEntry[]} normalized aliases
+ */
+function normalizeAlias(alias) {
+	return typeof alias === "object" && !Array.isArray(alias) && alias !== null
+		? Object.keys(alias).map((key) => {
+				/** @type {AliasOptionEntry} */
+				const obj = { name: key, onlyModule: false, alias: alias[key] };
+
+				if (/\$$/.test(key)) {
+					obj.onlyModule = true;
+					obj.name = key.slice(0, -1);
+				}
+
+				return obj;
+			})
+		: /** @type {AliasOptionEntry[]} */ (alias) || [];
+}
+
+/**
+ * Merging filtered elements
+ * @param {string[]} array source array
+ * @param {(item: string) => boolean} filter predicate
+ * @returns {(string | string[])[]} merge result
+ */
+function mergeFilteredToArray(array, filter) {
+	/** @type {(string | string[])[]} */
+	const result = [];
+	const set = new Set(array);
+
+	for (const item of set) {
+		if (filter(item)) {
+			const lastElement =
+				result.length > 0 ? result[result.length - 1] : undefined;
+			if (Array.isArray(lastElement)) {
+				lastElement.push(item);
+			} else {
+				result.push([item]);
+			}
+		} else {
+			result.push(item);
+		}
+	}
+
+	return result;
+}
+
+/**
+ * @param {UserResolveOptions} options input options
+ * @returns {ResolveOptions} output options
+ */
+function createOptions(options) {
+	const mainFieldsSet = new Set(options.mainFields || ["main"]);
+	/** @type {ResolveOptions["mainFields"]} */
+	const mainFields = [];
+
+	for (const item of mainFieldsSet) {
+		if (typeof item === "string") {
+			mainFields.push({
+				name: [item],
+				forceRelative: true,
+			});
+		} else if (Array.isArray(item)) {
+			mainFields.push({
+				name: item,
+				forceRelative: true,
+			});
+		} else {
+			mainFields.push({
+				name: Array.isArray(item.name) ? item.name : [item.name],
+				forceRelative: item.forceRelative,
+			});
+		}
+	}
+
+	return {
+		alias: normalizeAlias(options.alias),
+		fallback: normalizeAlias(options.fallback),
+		aliasFields: new Set(options.aliasFields),
+		cachePredicate:
+			options.cachePredicate ||
+			function trueFn() {
+				return true;
+			},
+		cacheWithContext:
+			typeof options.cacheWithContext !== "undefined"
+				? options.cacheWithContext
+				: true,
+		exportsFields: new Set(options.exportsFields || ["exports"]),
+		importsFields: new Set(options.importsFields || ["imports"]),
+		conditionNames: new Set(options.conditionNames),
+		descriptionFiles: [
+			...new Set(options.descriptionFiles || ["package.json"]),
+		],
+		enforceExtension:
+			options.enforceExtension === undefined
+				? Boolean(options.extensions && options.extensions.includes(""))
+				: options.enforceExtension,
+		extensions: new Set(options.extensions || [".js", ".json", ".node"]),
+		extensionAlias: options.extensionAlias
+			? Object.keys(options.extensionAlias).map((k) => ({
+					extension: k,
+					alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
+						k
+					],
+				}))
+			: [],
+		extensionAliasForExports: options.extensionAliasForExports || false,
+		fileSystem: options.useSyncFileSystemCalls
+			? new SyncAsyncFileSystemDecorator(
+					/** @type {SyncFileSystem} */ (
+						/** @type {unknown} */ (options.fileSystem)
+					),
+				)
+			: options.fileSystem,
+		unsafeCache:
+			options.unsafeCache && typeof options.unsafeCache !== "object"
+				? /** @type {Cache} */ ({})
+				: options.unsafeCache || false,
+		symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
+		resolver: options.resolver,
+		modules: mergeFilteredToArray(
+			Array.isArray(options.modules)
+				? options.modules
+				: options.modules
+					? [options.modules]
+					: ["node_modules"],
+			(item) => {
+				const type = getType(item);
+				return type === PathType.Normal || type === PathType.Relative;
+			},
+		),
+		mainFields,
+		mainFiles: new Set(options.mainFiles || ["index"]),
+		plugins: options.plugins || [],
+		pnpApi: processPnpApiOption(options.pnpApi),
+		roots: new Set(options.roots || undefined),
+		fullySpecified: options.fullySpecified || false,
+		resolveToContext: options.resolveToContext || false,
+		preferRelative: options.preferRelative || false,
+		preferAbsolute: options.preferAbsolute || false,
+		restrictions: new Set(options.restrictions),
+		tsconfig:
+			typeof options.tsconfig === "undefined" ? false : options.tsconfig,
+	};
+}
+
+/**
+ * @param {UserResolveOptions} options resolve options
+ * @returns {Resolver} created resolver
+ */
+module.exports.createResolver = function createResolver(options) {
+	const normalizedOptions = createOptions(options);
+
+	const {
+		alias,
+		fallback,
+		aliasFields,
+		extensionAliasForExports,
+		cachePredicate,
+		cacheWithContext,
+		conditionNames,
+		descriptionFiles,
+		enforceExtension,
+		exportsFields,
+		extensionAlias,
+		importsFields,
+		extensions,
+		fileSystem,
+		fullySpecified,
+		mainFields,
+		mainFiles,
+		modules,
+		plugins: userPlugins,
+		pnpApi,
+		resolveToContext,
+		preferRelative,
+		preferAbsolute,
+		symlinks,
+		unsafeCache,
+		resolver: customResolver,
+		restrictions,
+		roots,
+		tsconfig,
+	} = normalizedOptions;
+
+	const plugins = [...userPlugins];
+
+	const resolver =
+		customResolver || new Resolver(fileSystem, normalizedOptions);
+
+	// // pipeline ////
+
+	resolver.ensureHook("resolve");
+	resolver.ensureHook("internalResolve");
+	resolver.ensureHook("newInternalResolve");
+	resolver.ensureHook("importsResolve");
+	resolver.ensureHook("parsedResolve");
+	resolver.ensureHook("describedResolve");
+	resolver.ensureHook("rawResolve");
+	resolver.ensureHook("normalResolve");
+	resolver.ensureHook("internal");
+	resolver.ensureHook("rawModule");
+	resolver.ensureHook("alternateRawModule");
+	resolver.ensureHook("module");
+	resolver.ensureHook("resolveAsModule");
+	resolver.ensureHook("undescribedResolveInPackage");
+	resolver.ensureHook("resolveInPackage");
+	resolver.ensureHook("resolveInExistingDirectory");
+	resolver.ensureHook("importsFieldRelative");
+	if (extensionAliasForExports) {
+		resolver.ensureHook("exportsFieldRelative");
+	}
+	resolver.ensureHook("relative");
+	resolver.ensureHook("describedRelative");
+	resolver.ensureHook("directory");
+	resolver.ensureHook("undescribedExistingDirectory");
+	resolver.ensureHook("existingDirectory");
+	resolver.ensureHook("undescribedRawFile");
+	resolver.ensureHook("rawFile");
+	resolver.ensureHook("file");
+	resolver.ensureHook("finalFile");
+	resolver.ensureHook("existingFile");
+	resolver.ensureHook("resolved");
+
+	// TODO remove in next major
+	// cspell:word Interal
+	// Backward-compat
+	// @ts-expect-error
+	resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
+
+	// resolve
+	for (const { source, resolveOptions } of [
+		{ source: "resolve", resolveOptions: { fullySpecified } },
+		{ source: "internal-resolve", resolveOptions: { fullySpecified: false } },
+		// Entry point for non-relative targets from the imports field.
+		// Sets internal: false to prevent re-entering imports resolution,
+		// aligning with the Node.js ESM spec where PACKAGE_IMPORTS_RESOLVE
+		// does not recursively resolve # specifiers.
+		// https://nodejs.org/api/esm.html#resolution-algorithm-specification
+		{
+			source: "imports-resolve",
+			resolveOptions: { fullySpecified: false, internal: false },
+		},
+	]) {
+		plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
+	}
+
+	// parsed-resolve
+	plugins.push(
+		new DescriptionFilePlugin(
+			"parsed-resolve",
+			descriptionFiles,
+			false,
+			"described-resolve",
+		),
+	);
+	plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
+
+	// described-resolve
+	if (unsafeCache) {
+		plugins.push(
+			new UnsafeCachePlugin(
+				"described-resolve",
+				cachePredicate,
+				/** @type {import("./UnsafeCachePlugin").Cache} */ (unsafeCache),
+				cacheWithContext,
+				"raw-resolve",
+			),
+		);
+	} else {
+		plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
+	}
+	if (fallback.length > 0) {
+		plugins.push(
+			new AliasPlugin("described-resolve", fallback, "internal-resolve"),
+		);
+	}
+	// raw-resolve
+	if (alias.length > 0) {
+		plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
+	}
+	if (tsconfig) {
+		plugins.push(new TsconfigPathsPlugin(tsconfig));
+	}
+	for (const item of aliasFields) {
+		plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
+	}
+	for (const item of extensionAlias) {
+		plugins.push(
+			new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve"),
+		);
+	}
+	plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
+
+	// normal-resolve
+	if (preferRelative) {
+		plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
+	}
+	plugins.push(
+		new ConditionalPlugin(
+			"after-normal-resolve",
+			{ module: true },
+			"resolve as module",
+			false,
+			"raw-module",
+		),
+	);
+	plugins.push(
+		new ConditionalPlugin(
+			"after-normal-resolve",
+			{ internal: true },
+			"resolve as internal import",
+			false,
+			"internal",
+		),
+	);
+	if (preferAbsolute) {
+		plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
+	}
+	if (roots.size > 0) {
+		plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
+	}
+	if (!preferRelative && !preferAbsolute) {
+		plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
+	}
+
+	// internal
+	for (const importsField of importsFields) {
+		plugins.push(
+			new ImportsFieldPlugin(
+				"internal",
+				conditionNames,
+				importsField,
+				"imports-field-relative",
+				"imports-resolve",
+			),
+		);
+	}
+	// imports-field-relative: apply extensionAlias to paths produced by the
+	// imports field (TypeScript-style extension substitution for self-package
+	// imports like `#foo` -> `./foo.js` -> `./foo.ts`). Unlike the exports
+	// field, the imports field is internal to the package, so applying the
+	// consumer's extension aliases does not expose files the package author
+	// did not intend to export. See issue #413.
+	for (const item of extensionAlias) {
+		plugins.push(
+			new ExtensionAliasPlugin("imports-field-relative", item, "relative"),
+		);
+	}
+	plugins.push(new NextPlugin("imports-field-relative", "relative"));
+
+	// raw-module
+	for (const exportsField of exportsFields) {
+		plugins.push(
+			new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module"),
+		);
+	}
+	for (const item of modules) {
+		if (Array.isArray(item)) {
+			if (item.includes("node_modules") && pnpApi) {
+				plugins.push(
+					new ModulesInHierarchicalDirectoriesPlugin(
+						"raw-module",
+						item.filter((i) => i !== "node_modules"),
+						"module",
+					),
+				);
+				plugins.push(
+					new PnpPlugin(
+						"raw-module",
+						pnpApi,
+						"undescribed-resolve-in-package",
+						"alternate-raw-module",
+					),
+				);
+
+				plugins.push(
+					new ModulesInHierarchicalDirectoriesPlugin(
+						"alternate-raw-module",
+						["node_modules"],
+						"module",
+					),
+				);
+			} else {
+				plugins.push(
+					new ModulesInHierarchicalDirectoriesPlugin(
+						"raw-module",
+						item,
+						"module",
+					),
+				);
+			}
+		} else {
+			plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
+		}
+	}
+
+	// module
+	plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
+
+	// resolve-as-module
+	if (!resolveToContext) {
+		plugins.push(
+			new ConditionalPlugin(
+				"resolve-as-module",
+				{ directory: false, request: "." },
+				"single file module",
+				true,
+				"undescribed-raw-file",
+			),
+		);
+	}
+	plugins.push(
+		new DirectoryExistsPlugin(
+			"resolve-as-module",
+			"undescribed-resolve-in-package",
+		),
+	);
+
+	// undescribed-resolve-in-package
+	plugins.push(
+		new DescriptionFilePlugin(
+			"undescribed-resolve-in-package",
+			descriptionFiles,
+			false,
+			"resolve-in-package",
+		),
+	);
+	plugins.push(
+		new NextPlugin(
+			"after-undescribed-resolve-in-package",
+			"resolve-in-package",
+		),
+	);
+
+	// resolve-in-package
+	const exportsFieldTarget = extensionAliasForExports
+		? "exports-field-relative"
+		: "relative";
+	for (const exportsField of exportsFields) {
+		plugins.push(
+			new ExportsFieldPlugin(
+				"resolve-in-package",
+				conditionNames,
+				exportsField,
+				exportsFieldTarget,
+			),
+		);
+	}
+	plugins.push(
+		new NextPlugin("resolve-in-package", "resolve-in-existing-directory"),
+	);
+
+	// exports-field-relative (opt-in via `extensionAliasForExports`):
+	// apply `extensionAlias` to paths produced by the exports field. This is
+	// off by default to match Node.js (which does not substitute extensions on
+	// bare-module targets), and on opt-in aligns with TypeScript for packages
+	// that ship TS sources alongside the compiled JS they list in `exports`.
+	if (extensionAliasForExports) {
+		for (const item of extensionAlias) {
+			plugins.push(
+				new ExtensionAliasPlugin("exports-field-relative", item, "relative"),
+			);
+		}
+		plugins.push(new NextPlugin("exports-field-relative", "relative"));
+	}
+
+	// resolve-in-existing-directory
+	plugins.push(
+		new JoinRequestPlugin("resolve-in-existing-directory", "relative"),
+	);
+
+	// relative
+	plugins.push(
+		new DescriptionFilePlugin(
+			"relative",
+			descriptionFiles,
+			true,
+			"described-relative",
+		),
+	);
+	plugins.push(new NextPlugin("after-relative", "described-relative"));
+
+	// described-relative
+	if (resolveToContext) {
+		plugins.push(new NextPlugin("described-relative", "directory"));
+	} else {
+		plugins.push(
+			new ConditionalPlugin(
+				"described-relative",
+				{ directory: false },
+				null,
+				true,
+				"raw-file",
+			),
+		);
+		plugins.push(
+			new ConditionalPlugin(
+				"described-relative",
+				{ fullySpecified: false },
+				"as directory",
+				true,
+				"directory",
+			),
+		);
+	}
+
+	// directory
+	plugins.push(
+		new DirectoryExistsPlugin("directory", "undescribed-existing-directory"),
+	);
+
+	if (resolveToContext) {
+		// undescribed-existing-directory
+		plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
+	} else {
+		// undescribed-existing-directory
+		plugins.push(
+			new DescriptionFilePlugin(
+				"undescribed-existing-directory",
+				descriptionFiles,
+				false,
+				"existing-directory",
+			),
+		);
+		for (const item of mainFiles) {
+			plugins.push(
+				new UseFilePlugin(
+					"undescribed-existing-directory",
+					item,
+					"undescribed-raw-file",
+				),
+			);
+		}
+
+		// described-existing-directory
+		for (const item of mainFields) {
+			plugins.push(
+				new MainFieldPlugin(
+					"existing-directory",
+					item,
+					"resolve-in-existing-directory",
+				),
+			);
+		}
+		for (const item of mainFiles) {
+			plugins.push(
+				new UseFilePlugin("existing-directory", item, "undescribed-raw-file"),
+			);
+		}
+
+		// undescribed-raw-file
+		plugins.push(
+			new DescriptionFilePlugin(
+				"undescribed-raw-file",
+				descriptionFiles,
+				true,
+				"raw-file",
+			),
+		);
+		plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
+
+		// raw-file
+		plugins.push(
+			new ConditionalPlugin(
+				"raw-file",
+				{ fullySpecified: true },
+				null,
+				false,
+				"file",
+			),
+		);
+		if (!enforceExtension) {
+			plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
+		}
+		for (const item of extensions) {
+			plugins.push(new AppendPlugin("raw-file", item, "file"));
+		}
+
+		// file
+		if (alias.length > 0) {
+			plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
+		}
+		for (const item of aliasFields) {
+			plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
+		}
+		plugins.push(new NextPlugin("file", "final-file"));
+
+		// final-file
+		plugins.push(new FileExistsPlugin("final-file", "existing-file"));
+
+		// existing-file
+		if (symlinks) {
+			plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
+		}
+		plugins.push(new NextPlugin("existing-file", "resolved"));
+	}
+
+	const { resolved } =
+		/** @type {KnownHooks & EnsuredHooks} */
+		(resolver.hooks);
+
+	// resolved
+	if (restrictions.size > 0) {
+		plugins.push(new RestrictionsPlugin(resolved, restrictions));
+	}
+
+	plugins.push(new ResultPlugin(resolved));
+
+	// // RESOLVER ////
+
+	for (const plugin of plugins) {
+		if (typeof plugin === "function") {
+			/** @type {(this: Resolver, resolver: Resolver) => void} */
+			(plugin).call(resolver, resolver);
+		} else if (plugin) {
+			plugin.apply(resolver);
+		}
+	}
+
+	return resolver;
+};
Index: frontend/node_modules/enhanced-resolve/lib/RestrictionsPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/RestrictionsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/RestrictionsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,70 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+const slashCode = "/".charCodeAt(0);
+const backslashCode = "\\".charCodeAt(0);
+
+/**
+ * @param {string} path path
+ * @param {string} parent parent path
+ * @returns {boolean} true, if path is inside of parent
+ */
+const isInside = (path, parent) => {
+	if (!path.startsWith(parent)) return false;
+	if (path.length === parent.length) return true;
+	const charCode = path.charCodeAt(parent.length);
+	return charCode === slashCode || charCode === backslashCode;
+};
+
+module.exports = class RestrictionsPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {Set<string | RegExp>} restrictions restrictions
+	 */
+	constructor(source, restrictions) {
+		this.source = source;
+		this.restrictions = restrictions;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		resolver
+			.getHook(this.source)
+			.tapAsync("RestrictionsPlugin", (request, resolveContext, callback) => {
+				if (typeof request.path === "string") {
+					const { path } = request;
+					for (const rule of this.restrictions) {
+						if (typeof rule === "string") {
+							if (!isInside(path, rule)) {
+								if (resolveContext.log) {
+									resolveContext.log(
+										`${path} is not inside of the restriction ${rule}`,
+									);
+								}
+								return callback(null, null);
+							}
+						} else if (!rule.test(path)) {
+							if (resolveContext.log) {
+								resolveContext.log(
+									`${path} doesn't match the restriction ${rule}`,
+								);
+							}
+							return callback(null, null);
+						}
+					}
+				}
+
+				callback();
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/ResultPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/ResultPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/ResultPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,43 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class ResultPlugin {
+	/**
+	 * @param {ResolveStepHook} source source
+	 */
+	constructor(source) {
+		this.source = source;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		this.source.tapAsync(
+			"ResultPlugin",
+			(request, resolverContext, callback) => {
+				const obj = { ...request };
+				if (resolverContext.log) {
+					resolverContext.log(`reporting result ${obj.path}`);
+				}
+				resolver.hooks.result.callAsync(obj, resolverContext, (err) => {
+					if (err) return callback(err);
+					if (typeof resolverContext.yield === "function") {
+						resolverContext.yield(obj);
+						callback(null, null);
+					} else {
+						callback(null, obj);
+					}
+				});
+			},
+		);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/RootsPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/RootsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/RootsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,69 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+class RootsPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source hook
+	 * @param {Set<string>} roots roots
+	 * @param {string | ResolveStepHook} target target hook
+	 */
+	constructor(source, roots, target) {
+		this.roots = [...roots];
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+
+		resolver
+			.getHook(this.source)
+			.tapAsync("RootsPlugin", (request, resolveContext, callback) => {
+				const req = request.request;
+				if (!req) return callback();
+				if (!req.startsWith("/")) return callback();
+
+				forEachBail(
+					this.roots,
+					/**
+					 * @param {string} root root
+					 * @param {(err?: null | Error, result?: null | ResolveRequest) => void} callback callback
+					 * @returns {void}
+					 */
+					(root, callback) => {
+						const path = resolver.join(root, req.slice(1));
+						/** @type {ResolveRequest} */
+						const obj = {
+							...request,
+							path,
+							relativePath: request.relativePath && path,
+						};
+						resolver.doResolve(
+							target,
+							obj,
+							`root path ${root}`,
+							resolveContext,
+							callback,
+						);
+					},
+					callback,
+				);
+			});
+	}
+}
+
+module.exports = RootsPlugin;
Index: frontend/node_modules/enhanced-resolve/lib/SelfReferencePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/SelfReferencePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/SelfReferencePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,106 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const DescriptionFileUtils = require("./DescriptionFileUtils");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").JsonObject} JsonObject */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+const slashCode = "/".charCodeAt(0);
+
+// Sentinel stored in `_nameCache` when the description file either has no
+// exports field (so self-reference can't apply) or no string `name`.
+const NO_SELF_REF = Symbol("NoSelfRef");
+
+module.exports = class SelfReferencePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | string[]} fieldNamePath name path
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, fieldNamePath, target) {
+		this.source = source;
+		this.target = target;
+		this.fieldName = fieldNamePath;
+		// Self-reference needs both an exports field and a `"name"` string.
+		// Both are stable per description-file content, so cache the decision
+		// in one WeakMap: the resolved name when self-reference is possible,
+		// or `NO_SELF_REF` when it isn't. This skips the two per-resolve
+		// `DescriptionFileUtils.getField` walks for hot packages.
+		/** @type {WeakMap<JsonObject, string | typeof NO_SELF_REF>} */
+		this._nameCache = new WeakMap();
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("SelfReferencePlugin", (request, resolveContext, callback) => {
+				if (!request.descriptionFileData) return callback();
+
+				const req = request.request;
+				if (!req) return callback();
+
+				const { descriptionFileData } = request;
+				let name = this._nameCache.get(descriptionFileData);
+				if (name === undefined) {
+					// Feature is only enabled when an exports field is present
+					const exportsField = DescriptionFileUtils.getField(
+						descriptionFileData,
+						this.fieldName,
+					);
+					if (!exportsField) {
+						this._nameCache.set(descriptionFileData, NO_SELF_REF);
+						return callback();
+					}
+					const rawName = DescriptionFileUtils.getField(
+						descriptionFileData,
+						"name",
+					);
+					if (typeof rawName !== "string") {
+						this._nameCache.set(descriptionFileData, NO_SELF_REF);
+						return callback();
+					}
+					name = rawName;
+					this._nameCache.set(descriptionFileData, name);
+				} else if (name === NO_SELF_REF) {
+					return callback();
+				}
+
+				if (
+					req.startsWith(name) &&
+					(req.length === name.length ||
+						req.charCodeAt(name.length) === slashCode)
+				) {
+					const remainingRequest = `.${req.slice(name.length)}`;
+					/** @type {ResolveRequest} */
+					const obj = {
+						...request,
+						request: remainingRequest,
+						path: /** @type {string} */ (request.descriptionFileRoot),
+						relativePath: ".",
+					};
+
+					resolver.doResolve(
+						target,
+						obj,
+						"self reference",
+						resolveContext,
+						callback,
+					);
+				} else {
+					return callback();
+				}
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/SymlinkPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/SymlinkPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/SymlinkPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,121 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const forEachBail = require("./forEachBail");
+const { getPathsCached } = require("./getPaths");
+const { PathType, getType } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class SymlinkPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		const fs = resolver.fileSystem;
+		resolver
+			.getHook(this.source)
+			.tapAsync("SymlinkPlugin", (request, resolveContext, callback) => {
+				if (request.ignoreSymlinks) return callback();
+				const pathsResult = getPathsCached(
+					fs,
+					/** @type {string} */ (request.path),
+				);
+				const { paths, segments } = pathsResult;
+				// `pathsResult.segments` is shared across callers via the cache.
+				// The only place we need to mutate is `pathSegments[idx] = result`
+				// when `fs.readlink` succeeds — which is rare (the vast majority
+				// of paths contain no symlinks, e.g. every resolve on
+				// `cache-predicate`'s no-symlink fixture). Defer the copy until
+				// we actually see a symlink so the common no-symlink path stays
+				// allocation-free.
+				/** @type {string[] | null} */
+				let pathSegments = null;
+
+				let containsSymlink = false;
+				let idx = -1;
+				forEachBail(
+					paths,
+					/**
+					 * @param {string} path path
+					 * @param {(err?: null | Error, result?: null | number) => void} callback callback
+					 * @returns {void}
+					 */
+					(path, callback) => {
+						idx++;
+						if (resolveContext.fileDependencies) {
+							resolveContext.fileDependencies.add(path);
+						}
+						fs.readlink(path, (err, result) => {
+							if (!err && result) {
+								// First symlink seen — take our own copy now, so
+								// the cached `segments` array stays pristine for
+								// sibling resolves.
+								if (pathSegments === null) {
+									pathSegments = [...segments];
+								}
+								pathSegments[idx] = /** @type {string} */ (result);
+								containsSymlink = true;
+								// Shortcut when absolute symlink found
+								const resultType = getType(result.toString());
+								if (
+									resultType === PathType.AbsoluteWin ||
+									resultType === PathType.AbsolutePosix
+								) {
+									return callback(null, idx);
+								}
+							}
+							callback();
+						});
+					},
+					/**
+					 * @param {null | Error=} err error
+					 * @param {null | number=} idx result
+					 * @returns {void}
+					 */
+					(err, idx) => {
+						if (!containsSymlink) return callback();
+						// `containsSymlink === true` implies we took a copy in
+						// `pathSegments` already, so it's non-null. The copy is
+						// our own, so `slice` to trim is fine and spreading to
+						// "unshare" is no longer necessary.
+						const own = /** @type {string[]} */ (pathSegments);
+						const resultSegments =
+							typeof idx === "number" ? own.slice(0, idx + 1) : own;
+						const result = resultSegments.reduceRight((a, b) =>
+							resolver.join(a, b),
+						);
+						/** @type {ResolveRequest} */
+						const obj = {
+							...request,
+							path: result,
+						};
+						resolver.doResolve(
+							target,
+							obj,
+							`resolved symlink to ${result}`,
+							resolveContext,
+							callback,
+						);
+					},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/SyncAsyncFileSystemDecorator.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/SyncAsyncFileSystemDecorator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/SyncAsyncFileSystemDecorator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,258 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").StringCallback} StringCallback */
+/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
+
+// eslint-disable-next-line jsdoc/reject-function-type
+/** @typedef {Function} SyncOrAsyncFunction */
+// eslint-disable-next-line jsdoc/reject-any-type
+/** @typedef {any} ResultOfSyncOrAsyncFunction */
+
+/**
+ * @param {SyncFileSystem} fs file system implementation
+ * @constructor
+ */
+function SyncAsyncFileSystemDecorator(fs) {
+	this.fs = fs;
+
+	this.lstat = undefined;
+	this.lstatSync = undefined;
+	const { lstatSync } = fs;
+	if (lstatSync) {
+		this.lstat =
+			/** @type {FileSystem["lstat"]} */
+			(
+				(arg, options, callback) => {
+					let result;
+					try {
+						result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+							? lstatSync.call(fs, arg, options)
+							: lstatSync.call(fs, arg);
+					} catch (err) {
+						return (callback || options)(
+							/** @type {NodeJS.ErrnoException | null} */
+							(err),
+						);
+					}
+
+					(callback || options)(
+						null,
+						/** @type {ResultOfSyncOrAsyncFunction} */
+						(result),
+					);
+				}
+			);
+		this.lstatSync =
+			/** @type {SyncFileSystem["lstatSync"]} */
+			((arg, options) => lstatSync.call(fs, arg, options));
+	}
+
+	this.stat =
+		/** @type {FileSystem["stat"]} */
+		(
+			(arg, options, callback) => {
+				let result;
+				try {
+					result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+						? fs.statSync(arg, options)
+						: fs.statSync(arg);
+				} catch (err) {
+					return (callback || options)(
+						/** @type {NodeJS.ErrnoException | null} */
+						(err),
+					);
+				}
+
+				(callback || options)(
+					null,
+					/** @type {ResultOfSyncOrAsyncFunction} */
+					(result),
+				);
+			}
+		);
+	this.statSync =
+		/** @type {SyncFileSystem["statSync"]} */
+		((arg, options) => fs.statSync(arg, options));
+
+	this.readdir =
+		/** @type {FileSystem["readdir"]} */
+		(
+			(arg, options, callback) => {
+				let result;
+				try {
+					result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+						? fs.readdirSync(
+								arg,
+								/** @type {Exclude<Parameters<FileSystem["readdir"]>[1], (err: NodeJS.ErrnoException | null, files: string[]) => void>} */
+								(options),
+							)
+						: fs.readdirSync(arg);
+				} catch (err) {
+					return (callback || options)(
+						/** @type {NodeJS.ErrnoException | null} */
+						(err),
+						[],
+					);
+				}
+
+				(callback || options)(
+					null,
+					/** @type {ResultOfSyncOrAsyncFunction} */
+					(result),
+				);
+			}
+		);
+	this.readdirSync =
+		/** @type {SyncFileSystem["readdirSync"]} */
+		(
+			(arg, options) =>
+				fs.readdirSync(
+					arg,
+					/** @type {Parameters<SyncFileSystem["readdirSync"]>[1]} */ (options),
+				)
+		);
+
+	this.readFile =
+		/** @type {FileSystem["readFile"]} */
+		(
+			(arg, options, callback) => {
+				let result;
+				try {
+					result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+						? fs.readFileSync(arg, options)
+						: fs.readFileSync(arg);
+				} catch (err) {
+					return (callback || options)(
+						/** @type {NodeJS.ErrnoException | null} */
+						(err),
+					);
+				}
+
+				(callback || options)(
+					null,
+					/** @type {ResultOfSyncOrAsyncFunction} */
+					(result),
+				);
+			}
+		);
+	this.readFileSync =
+		/** @type {SyncFileSystem["readFileSync"]} */
+		((arg, options) => fs.readFileSync(arg, options));
+
+	this.readlink =
+		/** @type {FileSystem["readlink"]} */
+		(
+			(arg, options, callback) => {
+				let result;
+				try {
+					result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+						? fs.readlinkSync(
+								arg,
+								/** @type {Exclude<Parameters<FileSystem["readlink"]>[1], StringCallback>} */
+								(options),
+							)
+						: fs.readlinkSync(arg);
+				} catch (err) {
+					return (callback || options)(
+						/** @type {NodeJS.ErrnoException | null} */
+						(err),
+					);
+				}
+
+				(callback || options)(
+					null,
+					/** @type {ResultOfSyncOrAsyncFunction} */
+					(result),
+				);
+			}
+		);
+	this.readlinkSync =
+		/** @type {SyncFileSystem["readlinkSync"]} */
+		(
+			(arg, options) =>
+				fs.readlinkSync(
+					arg,
+					/** @type {Parameters<SyncFileSystem["readlinkSync"]>[1]} */ (
+						options
+					),
+				)
+		);
+
+	this.readJson = undefined;
+	this.readJsonSync = undefined;
+	const { readJsonSync } = fs;
+	if (readJsonSync) {
+		this.readJson =
+			/** @type {FileSystem["readJson"]} */
+			(
+				(arg, callback) => {
+					let result;
+					try {
+						result = readJsonSync.call(fs, arg);
+					} catch (err) {
+						return callback(
+							/** @type {NodeJS.ErrnoException | Error | null} */ (err),
+						);
+					}
+
+					callback(null, result);
+				}
+			);
+		this.readJsonSync =
+			/** @type {SyncFileSystem["readJsonSync"]} */
+			((arg) => readJsonSync.call(fs, arg));
+	}
+
+	this.realpath = undefined;
+	this.realpathSync = undefined;
+	const { realpathSync } = fs;
+	if (realpathSync) {
+		this.realpath =
+			/** @type {FileSystem["realpath"]} */
+			(
+				(arg, options, callback) => {
+					let result;
+					try {
+						result = /** @type {SyncOrAsyncFunction | undefined} */ (callback)
+							? realpathSync.call(
+									fs,
+									arg,
+									/** @type {Exclude<Parameters<NonNullable<FileSystem["realpath"]>>[1], StringCallback>} */
+									(options),
+								)
+							: realpathSync.call(fs, arg);
+					} catch (err) {
+						return (callback || options)(
+							/** @type {NodeJS.ErrnoException | null} */
+							(err),
+						);
+					}
+
+					(callback || options)(
+						null,
+						/** @type {ResultOfSyncOrAsyncFunction} */
+						(result),
+					);
+				}
+			);
+		this.realpathSync =
+			/** @type {SyncFileSystem["realpathSync"]} */
+			(
+				(arg, options) =>
+					realpathSync.call(
+						fs,
+						arg,
+						/** @type {Parameters<NonNullable<SyncFileSystem["realpathSync"]>>[1]} */
+						(options),
+					)
+			);
+	}
+}
+
+module.exports = SyncAsyncFileSystemDecorator;
Index: frontend/node_modules/enhanced-resolve/lib/TryNextPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/TryNextPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/TryNextPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,41 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class TryNextPlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string} message message
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, message, target) {
+		this.source = source;
+		this.message = message;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("TryNextPlugin", (request, resolveContext, callback) => {
+				resolver.doResolve(
+					target,
+					request,
+					this.message,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,865 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+*/
+
+"use strict";
+
+const { aliasResolveHandler, compileAliasOptions } = require("./AliasUtils");
+const { modulesResolveHandler } = require("./ModulesUtils");
+const { readJson } = require("./util/fs");
+const { PathType: _PathType, isSubPath, normalize } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./AliasUtils").AliasOption} AliasOption */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").TsconfigPathsData} TsconfigPathsData */
+/** @typedef {import("./Resolver").TsconfigPathsMap} TsconfigPathsMap */
+/** @typedef {import("./ResolverFactory").TsconfigOptions} TsconfigOptions */
+
+// Sentinel stored in `_contextSelectionCache` for `requestPath`s whose
+// scan returned `null` ("no context matched"). Using a non-null marker
+// lets the cache-hit path be a single `Map.get()` — we distinguish
+// "cached null" from "not cached yet" without a second `has` lookup.
+const NULL_CONTEXT = Symbol("NULL_CONTEXT");
+
+/**
+ * Per-`TsconfigPathsMap` memoization of `_selectPathsDataForContext`.
+ *
+ * Real-world builds resolve hundreds of requests per source file (every
+ * import in the file), and webpack-style resolvers walk the same
+ * `requestPath` (= source-file directory) for each one. Without this
+ * cache every resolve re-scans the full `contextList` even though the
+ * answer is invariant for a given `(map, requestPath)` pair.
+ *
+ * The outer key is the `TsconfigPathsMap` itself — rebuilt on every
+ * tsconfig change — so a `WeakMap` lets the inner map be collected
+ * automatically once the map goes away. The inner Map is keyed by
+ * `requestPath` (string); a `Symbol` sentinel stands in for "no
+ * matching context" so `Map.get` alone distinguishes the three states
+ * (cached data / cached null / not cached).
+ * @type {WeakMap<TsconfigPathsMap, Map<string, TsconfigPathsData | typeof NULL_CONTEXT>>}
+ */
+const _contextSelectionCache = new WeakMap();
+
+/**
+ * @typedef {object} TsconfigCompilerOptions
+ * @property {string=} baseUrl Base URL for resolving paths
+ * @property {{ [key: string]: string[] }=} paths TypeScript paths mapping
+ */
+
+/**
+ * @typedef {object} TsconfigReference
+ * @property {string} path Path to the referenced project
+ */
+
+/**
+ * @typedef {object} Tsconfig
+ * @property {TsconfigCompilerOptions=} compilerOptions Compiler options
+ * @property {string | string[]=} extends Extended configuration paths
+ * @property {TsconfigReference[]=} references Project references
+ */
+
+const DEFAULT_CONFIG_FILE = "tsconfig.json";
+
+const READ_JSON_OPTIONS = { stripComments: true };
+
+// Trailing `/*` or `\*` segment of a tsconfig `paths` mapping (e.g.
+// `./src/*` → `./src`). Hoisted so we don't allocate a fresh regex per
+// path entry on every tsconfig load — and so the same regex object can be
+// reused for the matching `test` + `replace` pair below.
+const WILDCARD_TAIL_RE = /[/\\]\*$/;
+
+/**
+ * @param {string} pattern Path pattern
+ * @returns {number} Length of the prefix
+ */
+function getPrefixLength(pattern) {
+	const prefixLength = pattern.indexOf("*");
+	if (prefixLength === -1) {
+		return pattern.length;
+	}
+	return prefixLength;
+}
+
+/**
+ * Sort path patterns.
+ * If a module name can be matched with multiple patterns then pattern with the longest prefix will be picked.
+ * @param {string[]} arr Array of path patterns
+ * @returns {string[]} Array of path patterns sorted by longest prefix
+ */
+function sortByLongestPrefix(arr) {
+	return [...arr].sort((a, b) => getPrefixLength(b) - getPrefixLength(a));
+}
+
+/**
+ * Merge two tsconfig objects
+ * @param {Tsconfig | null} base base config
+ * @param {Tsconfig | null} config config to merge
+ * @returns {Tsconfig} merged config
+ */
+function mergeTsconfigs(base, config) {
+	base = base || {};
+	config = config || {};
+
+	return {
+		...base,
+		...config,
+		compilerOptions: {
+			.../** @type {TsconfigCompilerOptions} */ (base.compilerOptions),
+			.../** @type {TsconfigCompilerOptions} */ (config.compilerOptions),
+		},
+	};
+}
+
+/**
+ * Substitute ${configDir} template variable in path
+ * @param {string} pathValue the path value
+ * @param {string} configDir the config directory
+ * @returns {string} the path with substituted template
+ */
+function substituteConfigDir(pathValue, configDir) {
+	// eslint-disable-next-line no-template-curly-in-string
+	if (!pathValue.includes("${configDir}")) return pathValue;
+	return pathValue.replace(/\$\{configDir\}/g, configDir);
+}
+
+/**
+ * Convert tsconfig paths to resolver options
+ * @param {string} configDir Config file directory
+ * @param {{ [key: string]: string[] }} paths TypeScript paths mapping
+ * @param {Resolver} resolver resolver instance
+ * @param {string=} baseUrl Base URL for resolving paths (relative to configDir)
+ * @returns {TsconfigPathsData} the resolver options
+ */
+function tsconfigPathsToResolveOptions(configDir, paths, resolver, baseUrl) {
+	// Calculate absolute base URL
+	const absoluteBaseUrl = !baseUrl
+		? configDir
+		: resolver.join(configDir, baseUrl);
+
+	/** @type {string[]} */
+	const sortedKeys = sortByLongestPrefix(Object.keys(paths));
+	/** @type {AliasOption[]} */
+	const alias = [];
+	/** @type {string[]} */
+	const modules = [];
+
+	for (const pattern of sortedKeys) {
+		const mappings = paths[pattern];
+		// Substitute ${configDir} in path mappings
+		const absolutePaths = mappings.map((mapping) => {
+			const substituted = substituteConfigDir(mapping, configDir);
+			return resolver.join(absoluteBaseUrl, substituted);
+		});
+
+		if (absolutePaths.length > 0) {
+			if (pattern === "*") {
+				// Pull `dir/*` entries directly into `modules` with their
+				// trailing wildcard stripped, skipping anything else. The
+				// previous `.map(...).filter(Boolean)` form allocated two
+				// throwaway arrays plus a spread iterator per `*` mapping.
+				for (let j = 0; j < absolutePaths.length; j++) {
+					const dir = absolutePaths[j];
+					if (WILDCARD_TAIL_RE.test(dir)) {
+						modules.push(dir.replace(WILDCARD_TAIL_RE, ""));
+					}
+				}
+			} else {
+				alias.push({ name: pattern, alias: absolutePaths });
+			}
+		}
+	}
+
+	if (baseUrl && absoluteBaseUrl && !modules.includes(absoluteBaseUrl)) {
+		modules.push(absoluteBaseUrl);
+	}
+
+	return {
+		alias: compileAliasOptions(resolver, alias),
+		modules,
+	};
+}
+
+/**
+ * Get the base context for the current project
+ * @param {string} context the context
+ * @param {Resolver} resolver resolver instance
+ * @param {string=} baseUrl base URL for resolving paths
+ * @returns {string} the base context
+ */
+function getAbsoluteBaseUrl(context, resolver, baseUrl) {
+	return !baseUrl ? context : resolver.join(context, baseUrl);
+}
+
+/**
+ * @param {TsconfigPathsData} main main paths data
+ * @param {string} mainContext main context
+ * @param {{ [baseUrl: string]: TsconfigPathsData }} refs references map
+ * @param {Set<string>} fileDependencies file dependencies
+ * @returns {TsconfigPathsMap} the tsconfig paths map
+ */
+function buildTsconfigPathsMap(main, mainContext, refs, fileDependencies) {
+	const allContexts = /** @type {{ [context: string]: TsconfigPathsData }} */ ({
+		[mainContext]: main,
+		...refs,
+	});
+	// Precompute the key list once per tsconfig load. `_selectPathsDataForContext`
+	// runs per resolve and otherwise would call `Object.entries(allContexts)`
+	// each time, allocating a fresh [key, value][] array.
+	const contextList = Object.keys(allContexts);
+	return {
+		main,
+		mainContext,
+		refs,
+		allContexts,
+		contextList,
+		fileDependencies,
+	};
+}
+
+module.exports = class TsconfigPathsPlugin {
+	/**
+	 * @param {true | string | TsconfigOptions} configFileOrOptions tsconfig file path or options object
+	 */
+	constructor(configFileOrOptions) {
+		if (
+			typeof configFileOrOptions === "object" &&
+			configFileOrOptions !== null
+		) {
+			// Options object format
+			const { configFile } = configFileOrOptions;
+			/** @type {boolean} */
+			this.isAutoConfigFile = typeof configFile !== "string";
+			/** @type {string} */
+			this.configFile = this.isAutoConfigFile
+				? DEFAULT_CONFIG_FILE
+				: /** @type {string} */ (configFile);
+			/** @type {string[] | "auto"} */
+			if (Array.isArray(configFileOrOptions.references)) {
+				/** @type {TsconfigReference[] | "auto"} */
+				this.references = configFileOrOptions.references.map((ref) => ({
+					path: ref,
+				}));
+			} else if (configFileOrOptions.references === "auto") {
+				this.references = "auto";
+			} else {
+				this.references = [];
+			}
+			/** @type {string | undefined} */
+			this.baseUrl = configFileOrOptions.baseUrl;
+		} else {
+			/** @type {boolean} */
+			this.isAutoConfigFile = configFileOrOptions === true;
+			/** @type {string} */
+			this.configFile = this.isAutoConfigFile
+				? DEFAULT_CONFIG_FILE
+				: /** @type {string} */ (configFileOrOptions);
+			/** @type {TsconfigReference[] | "auto"} */
+			this.references = [];
+			/** @type {string | undefined} */
+			this.baseUrl = undefined;
+		}
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const aliasTarget = resolver.ensureHook("internal-resolve");
+		const moduleTarget = resolver.ensureHook("module");
+
+		resolver
+			.getHook("raw-resolve")
+			.tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => {
+				this._getTsconfigPathsMap(
+					resolver,
+					request,
+					resolveContext,
+					(err, tsconfigPathsMap) => {
+						if (err) return callback(err);
+						if (!tsconfigPathsMap) return callback();
+
+						const selectedData = this._selectPathsDataForContext(
+							request.path,
+							tsconfigPathsMap,
+						);
+
+						if (!selectedData) return callback();
+
+						aliasResolveHandler(
+							resolver,
+							selectedData.alias,
+							aliasTarget,
+							request,
+							resolveContext,
+							(err, result) => {
+								if (err) return callback(err);
+								if (result) return callback(null, result);
+								// https://github.com/webpack/webpack/issues/20944
+								// Unlike resolve.alias, tsconfig paths should fall through
+								// when a pattern matches but the mapped path does not exist
+								// (matching TypeScript's native resolution behavior).
+								return callback();
+							},
+						);
+					},
+				);
+			});
+
+		resolver
+			.getHook("raw-module")
+			.tapAsync("TsconfigPathsPlugin", (request, resolveContext, callback) => {
+				this._getTsconfigPathsMap(
+					resolver,
+					request,
+					resolveContext,
+					(err, tsconfigPathsMap) => {
+						if (err) return callback(err);
+						if (!tsconfigPathsMap) return callback();
+
+						const selectedData = this._selectPathsDataForContext(
+							request.path,
+							tsconfigPathsMap,
+						);
+
+						if (!selectedData) return callback();
+
+						modulesResolveHandler(
+							resolver,
+							selectedData.modules,
+							moduleTarget,
+							request,
+							resolveContext,
+							callback,
+						);
+					},
+				);
+			});
+	}
+
+	/**
+	 * Get TsconfigPathsMap for the request (with caching)
+	 * @param {Resolver} resolver the resolver
+	 * @param {ResolveRequest} request the request
+	 * @param {ResolveContext} resolveContext the resolve context
+	 * @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback
+	 * @returns {void}
+	 */
+	_getTsconfigPathsMap(resolver, request, resolveContext, callback) {
+		if (typeof request.tsconfigPathsMap !== "undefined") {
+			const cached = request.tsconfigPathsMap;
+			if (!cached) return callback(null, null);
+			if (resolveContext.fileDependencies) {
+				for (const fileDependency of cached.fileDependencies) {
+					resolveContext.fileDependencies.add(fileDependency);
+				}
+			}
+			return callback(null, cached);
+		}
+
+		if (this.isAutoConfigFile) {
+			this._findTsconfigUpward(
+				resolver,
+				request.path || process.cwd(),
+				(err, result) => {
+					if (err) {
+						request.tsconfigPathsMap = null;
+						return callback(err);
+					}
+					if (!result) {
+						request.tsconfigPathsMap = null;
+						return callback(null, null);
+					}
+					const map = /** @type {TsconfigPathsMap} */ (result);
+					request.tsconfigPathsMap = map;
+					if (resolveContext.fileDependencies) {
+						for (const fileDependency of map.fileDependencies) {
+							resolveContext.fileDependencies.add(fileDependency);
+						}
+					}
+					callback(null, map);
+				},
+			);
+			return;
+		}
+
+		const absTsconfigPath = resolver.join(
+			request.path || process.cwd(),
+			this.configFile,
+		);
+		this._loadTsconfigPathsMap(resolver, absTsconfigPath, (err, result) => {
+			if (err) {
+				request.tsconfigPathsMap = null;
+				return callback(err);
+			}
+
+			const map = /** @type {TsconfigPathsMap} */ (result);
+			request.tsconfigPathsMap = map;
+			if (resolveContext.fileDependencies) {
+				for (const fileDependency of map.fileDependencies) {
+					resolveContext.fileDependencies.add(fileDependency);
+				}
+			}
+			callback(null, map);
+		});
+	}
+
+	/**
+	 * Walk up from startDir to the filesystem root looking for tsconfig.json.
+	 * Like TypeScript's own `findConfigFile` / `forEachAncestorDirectory`.
+	 * @param {Resolver} resolver the resolver
+	 * @param {string} startDir the directory to start searching from
+	 * @param {(err: Error | null, result?: TsconfigPathsMap | null) => void} callback the callback
+	 * @returns {void}
+	 */
+	_findTsconfigUpward(resolver, startDir, callback) {
+		const { fileSystem } = resolver;
+		const configFileName = this.configFile;
+
+		/**
+		 * @param {string} dir current directory
+		 */
+		const check = (dir) => {
+			const candidate = resolver.join(dir, configFileName);
+			fileSystem.stat(candidate, (statErr) => {
+				if (!statErr) {
+					// Found — load it
+					this._loadTsconfigPathsMap(resolver, candidate, (loadErr, result) => {
+						if (loadErr) return callback(loadErr);
+						callback(null, result);
+					});
+					return;
+				}
+				// Not found — move to parent
+				const parentDir = resolver.dirname(dir);
+				if (parentDir === dir) {
+					// Reached filesystem root, no tsconfig.json found
+					return callback(null, null);
+				}
+				check(parentDir);
+			});
+		};
+
+		check(startDir);
+	}
+
+	/**
+	 * Load tsconfig.json and build complete TsconfigPathsMap
+	 * Includes main project paths and all referenced projects
+	 * @param {Resolver} resolver the resolver
+	 * @param {string} absTsconfigPath absolute path to tsconfig.json
+	 * @param {(err: Error | null, result?: TsconfigPathsMap) => void} callback the callback
+	 * @returns {void}
+	 */
+	_loadTsconfigPathsMap(resolver, absTsconfigPath, callback) {
+		/** @type {Set<string>} */
+		const fileDependencies = new Set();
+
+		this._loadTsconfig(
+			resolver,
+			absTsconfigPath,
+			fileDependencies,
+			undefined,
+			(err, config) => {
+				if (err) return callback(err);
+
+				const cfg = /** @type {Tsconfig} */ (config);
+				const compilerOptions = cfg.compilerOptions || {};
+				const mainContext = resolver.dirname(absTsconfigPath);
+
+				const baseUrl =
+					this.baseUrl !== undefined ? this.baseUrl : compilerOptions.baseUrl;
+
+				const main = tsconfigPathsToResolveOptions(
+					mainContext,
+					compilerOptions.paths || {},
+					resolver,
+					baseUrl,
+				);
+				/** @type {{ [baseUrl: string]: TsconfigPathsData }} */
+				const refs = {};
+
+				let referencesToUse = null;
+				if (this.references === "auto") {
+					referencesToUse = cfg.references;
+				} else if (Array.isArray(this.references)) {
+					referencesToUse = this.references;
+				}
+
+				if (!Array.isArray(referencesToUse)) {
+					return callback(
+						null,
+						buildTsconfigPathsMap(main, mainContext, refs, fileDependencies),
+					);
+				}
+
+				this._loadTsconfigReferences(
+					resolver,
+					mainContext,
+					referencesToUse,
+					fileDependencies,
+					refs,
+					(refErr) => {
+						if (refErr) return callback(refErr);
+						callback(
+							null,
+							buildTsconfigPathsMap(main, mainContext, refs, fileDependencies),
+						);
+					},
+				);
+			},
+		);
+	}
+
+	/**
+	 * Select the correct TsconfigPathsData based on request.path (context-aware)
+	 * Matches the behavior of tsconfig-paths-webpack-plugin
+	 * @param {string | false} requestPath the request path
+	 * @param {TsconfigPathsMap} tsconfigPathsMap the tsconfig paths map
+	 * @returns {TsconfigPathsData | null} the selected paths data
+	 */
+	_selectPathsDataForContext(requestPath, tsconfigPathsMap) {
+		const { main, allContexts, contextList } = tsconfigPathsMap;
+		if (!requestPath) {
+			return main;
+		}
+		// Single-context tsconfigs (no project references) hit the loop
+		// below at most once; in that case the cache lookup costs more
+		// than the loop itself. Only memoize when there are 2+ contexts
+		// — that's the monorepo / project-references shape where the
+		// scan actually walks multiple entries per resolve and the
+		// `(map, requestPath)` answer can be reused.
+		/** @type {Map<string, TsconfigPathsData | typeof NULL_CONTEXT> | undefined} */
+		let perMap;
+		if (contextList.length >= 2) {
+			perMap = _contextSelectionCache.get(tsconfigPathsMap);
+			if (perMap !== undefined) {
+				const cached = perMap.get(requestPath);
+				if (cached !== undefined) {
+					return cached === NULL_CONTEXT
+						? null
+						: /** @type {TsconfigPathsData} */ (cached);
+				}
+			} else {
+				perMap = new Map();
+				_contextSelectionCache.set(tsconfigPathsMap, perMap);
+			}
+		}
+		let longestMatchContext = null;
+		let longestMatchLength = 0;
+		// Iterate the pre-computed key list (the previous
+		// `Object.entries(allContexts)` form allocated a fresh
+		// `[key, value][]` per resolve). Defer the `allContexts[context]`
+		// lookup to after we know the context actually matches — non-matches
+		// are the common case and don't need the property access.
+		for (let i = 0; i < contextList.length; i++) {
+			const context = contextList[i];
+			if (context === requestPath) {
+				const exact = allContexts[context];
+				if (perMap !== undefined) perMap.set(requestPath, exact);
+				return exact;
+			}
+			// Cheap integer-compare gate first: a context can only beat the
+			// current longest match if its own length is strictly greater.
+			// Skipping `isSubPath` (a `startsWith` + char-code probe) when the
+			// length already disqualifies the candidate avoids the per-resolve
+			// scan over every shorter context.
+			if (
+				context.length > longestMatchLength &&
+				isSubPath(context, requestPath)
+			) {
+				longestMatchContext = context;
+				longestMatchLength = context.length;
+			}
+		}
+		const result =
+			longestMatchContext === null ? null : allContexts[longestMatchContext];
+		if (perMap !== undefined) {
+			perMap.set(requestPath, result === null ? NULL_CONTEXT : result);
+		}
+		return result;
+	}
+
+	/**
+	 * Load tsconfig from extends path
+	 * @param {Resolver} resolver the resolver
+	 * @param {string} configFilePath current config file path
+	 * @param {string} extendedConfigValue extends value
+	 * @param {Set<string>} fileDependencies the file dependencies
+	 * @param {Set<string>} visitedConfigPaths config paths being loaded (for circular extends detection)
+	 * @param {(err: Error | null, result?: Tsconfig) => void} callback callback
+	 * @returns {void}
+	 */
+	_loadTsconfigFromExtends(
+		resolver,
+		configFilePath,
+		extendedConfigValue,
+		fileDependencies,
+		visitedConfigPaths,
+		callback,
+	) {
+		const { fileSystem } = resolver;
+		const currentDir = resolver.dirname(configFilePath);
+
+		// Substitute ${configDir} in extends path
+		extendedConfigValue = substituteConfigDir(extendedConfigValue, currentDir);
+
+		// Remember the original value before potentially appending .json
+		const originalExtendedConfigValue = extendedConfigValue;
+
+		if (
+			typeof extendedConfigValue === "string" &&
+			!extendedConfigValue.includes(".json")
+		) {
+			extendedConfigValue += ".json";
+		}
+
+		const initialExtendedConfigPath = resolver.join(
+			currentDir,
+			extendedConfigValue,
+		);
+
+		fileSystem.stat(initialExtendedConfigPath, (existsErr) => {
+			let extendedConfigPath = initialExtendedConfigPath;
+			if (existsErr) {
+				// Handle scoped package extends like "@scope/name" (no sub-path):
+				// "@scope/name" should resolve to node_modules/@scope/name/tsconfig.json,
+				// not node_modules/@scope/name.json
+				// See: test/fixtures/tsconfig-paths/extends-pkg-entry/
+				if (
+					typeof originalExtendedConfigValue === "string" &&
+					originalExtendedConfigValue.startsWith("@") &&
+					originalExtendedConfigValue.split("/").length === 2
+				) {
+					extendedConfigPath = resolver.join(
+						currentDir,
+						normalize(
+							`node_modules/${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`,
+						),
+					);
+				} else if (extendedConfigValue.includes("/")) {
+					// Handle package sub-path extends like "react/tsconfig":
+					// "react/tsconfig" resolves to node_modules/react/tsconfig.json
+					// See: test/fixtures/tsconfig-paths/extends-npm/
+					extendedConfigPath = resolver.join(
+						currentDir,
+						normalize(`node_modules/${extendedConfigValue}`),
+					);
+				} else if (
+					!originalExtendedConfigValue.startsWith(".") &&
+					!originalExtendedConfigValue.startsWith("/")
+				) {
+					// Handle unscoped package extends like "my-base-config" (no sub-path):
+					// "my-base-config" should resolve to node_modules/my-base-config/tsconfig.json
+					extendedConfigPath = resolver.join(
+						currentDir,
+						normalize(
+							`node_modules/${originalExtendedConfigValue}/${DEFAULT_CONFIG_FILE}`,
+						),
+					);
+				}
+			}
+
+			this._loadTsconfig(
+				resolver,
+				extendedConfigPath,
+				fileDependencies,
+				visitedConfigPaths,
+				(err, config) => {
+					if (err) return callback(err);
+
+					const cfg = /** @type {Tsconfig} */ (config);
+					const compilerOptions = cfg.compilerOptions || {
+						baseUrl: undefined,
+					};
+
+					if (compilerOptions.baseUrl) {
+						const extendedConfigDir = resolver.dirname(extendedConfigPath);
+						compilerOptions.baseUrl = getAbsoluteBaseUrl(
+							extendedConfigDir,
+							resolver,
+							compilerOptions.baseUrl,
+						);
+					}
+
+					delete cfg.references;
+
+					callback(null, cfg);
+				},
+			);
+		});
+	}
+
+	/**
+	 * Load referenced tsconfig projects and store in referenceMatchMap
+	 * Simple implementation matching tsconfig-paths-webpack-plugin:
+	 * Just load each reference and store independently
+	 * @param {Resolver} resolver the resolver
+	 * @param {string} context the context
+	 * @param {TsconfigReference[]} references array of references
+	 * @param {Set<string>} fileDependencies the file dependencies
+	 * @param {{ [baseUrl: string]: TsconfigPathsData }} referenceMatchMap the map to populate
+	 * @param {(err: Error | null) => void} callback callback
+	 * @param {Set<string>=} visitedRefPaths visited reference config paths (for circular reference detection)
+	 * @returns {void}
+	 */
+	_loadTsconfigReferences(
+		resolver,
+		context,
+		references,
+		fileDependencies,
+		referenceMatchMap,
+		callback,
+		visitedRefPaths,
+	) {
+		if (references.length === 0) return callback(null);
+
+		const visited = visitedRefPaths || new Set();
+		let pending = references.length;
+		const finishOne = () => {
+			if (--pending === 0) callback(null);
+		};
+
+		for (const ref of references) {
+			const refPath = substituteConfigDir(ref.path, context);
+			const refConfigPath = resolver.join(
+				resolver.join(context, refPath),
+				DEFAULT_CONFIG_FILE,
+			);
+
+			if (visited.has(refConfigPath)) {
+				finishOne();
+				continue;
+			}
+			visited.add(refConfigPath);
+
+			this._loadTsconfig(
+				resolver,
+				refConfigPath,
+				fileDependencies,
+				undefined,
+				(err, refConfig) => {
+					// Failures are swallowed to match tsconfig-paths-webpack-plugin:
+					// a broken reference must not abort the main project's resolution.
+					if (err) return finishOne();
+
+					const cfg = /** @type {Tsconfig} */ (refConfig);
+					if (cfg.compilerOptions && cfg.compilerOptions.paths) {
+						const refContext = resolver.dirname(refConfigPath);
+
+						referenceMatchMap[refContext] = tsconfigPathsToResolveOptions(
+							refContext,
+							cfg.compilerOptions.paths || {},
+							resolver,
+							cfg.compilerOptions.baseUrl,
+						);
+					}
+
+					if (this.references === "auto" && Array.isArray(cfg.references)) {
+						this._loadTsconfigReferences(
+							resolver,
+							resolver.dirname(refConfigPath),
+							cfg.references,
+							fileDependencies,
+							referenceMatchMap,
+							finishOne,
+							visited,
+						);
+					} else {
+						finishOne();
+					}
+				},
+			);
+		}
+	}
+
+	/**
+	 * Load tsconfig.json with extends support
+	 * @param {Resolver} resolver the resolver
+	 * @param {string} configFilePath absolute path to tsconfig.json
+	 * @param {Set<string>} fileDependencies the file dependencies
+	 * @param {Set<string> | undefined} visitedConfigPaths config paths being loaded (for circular extends detection)
+	 * @param {(err: Error | null, result?: Tsconfig) => void} callback callback
+	 * @returns {void}
+	 */
+	_loadTsconfig(
+		resolver,
+		configFilePath,
+		fileDependencies,
+		visitedConfigPaths,
+		callback,
+	) {
+		const visited = visitedConfigPaths || new Set();
+
+		if (visited.has(configFilePath)) {
+			return callback(null, /** @type {Tsconfig} */ ({}));
+		}
+		visited.add(configFilePath);
+
+		readJson(
+			resolver.fileSystem,
+			configFilePath,
+			READ_JSON_OPTIONS,
+			(err, parsed) => {
+				if (err) return callback(/** @type {Error} */ (err));
+
+				const config = /** @type {Tsconfig} */ (parsed);
+				fileDependencies.add(configFilePath);
+
+				const extendedConfig = config.extends;
+				if (!extendedConfig) return callback(null, config);
+
+				if (!Array.isArray(extendedConfig)) {
+					this._loadTsconfigFromExtends(
+						resolver,
+						configFilePath,
+						extendedConfig,
+						fileDependencies,
+						visited,
+						(extErr, extendedTsconfig) => {
+							if (extErr) return callback(extErr);
+							callback(
+								null,
+								mergeTsconfigs(
+									/** @type {Tsconfig} */ (extendedTsconfig),
+									config,
+								),
+							);
+						},
+					);
+					return;
+				}
+
+				/** @type {Tsconfig} */
+				let base = {};
+				let i = 0;
+				const next = () => {
+					if (i >= extendedConfig.length) {
+						return callback(null, mergeTsconfigs(base, config));
+					}
+					this._loadTsconfigFromExtends(
+						resolver,
+						configFilePath,
+						extendedConfig[i++],
+						fileDependencies,
+						visited,
+						(extErr, extendedTsconfig) => {
+							if (extErr) return callback(extErr);
+							base = mergeTsconfigs(
+								base,
+								/** @type {Tsconfig} */ (extendedTsconfig),
+							);
+							next();
+						},
+					);
+				};
+				next();
+			},
+		);
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/UnsafeCachePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/UnsafeCachePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/UnsafeCachePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,200 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { isRelativeRequest } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+/** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */
+/** @typedef {{ [k: string]: undefined | ResolveRequest | ResolveRequest[] }} Cache */
+
+/**
+ * @param {string} relativePath relative path from package root
+ * @param {string} request relative request
+ * @param {Resolver} resolver resolver instance
+ * @returns {string} normalized request with a preserved leading dot
+ */
+function joinRelativePreservingLeadingDot(relativePath, request, resolver) {
+	const normalized = resolver.join(relativePath, request);
+	return isRelativeRequest(normalized) ? normalized : `./${normalized}`;
+}
+
+/**
+ * @param {ResolveRequest} request request
+ * @returns {string | false | undefined} normalized path
+ */
+function getCachePath(request) {
+	if (request.descriptionFileRoot && !request.module) {
+		return request.descriptionFileRoot;
+	}
+	return request.path;
+}
+
+/**
+ * @param {ResolveRequest} request request
+ * @param {Resolver} resolver resolver instance
+ * @returns {string | undefined} normalized request string
+ */
+function getCacheRequest(request, resolver) {
+	const requestString = request.request;
+	if (
+		!requestString ||
+		!request.relativePath ||
+		!isRelativeRequest(requestString)
+	) {
+		return requestString;
+	}
+	return joinRelativePreservingLeadingDot(
+		request.relativePath,
+		requestString,
+		resolver,
+	);
+}
+
+// Cache-key separator: `\0` is safe because paths, requests, queries and
+// fragments produced by `parseIdentifier` never contain a raw NUL (the
+// \0-escape in identifier.js is decoded back to the original char), and the
+// context, when included, is passed through `JSON.stringify`, which escapes
+// any NUL to \u0000.
+// const SEP = "\0";
+
+/**
+ * Build the cache id for a request. Called on every `described-resolve`
+ * invocation when `unsafeCache` is on, so it's a hot path.
+ *
+ * Equivalent in meaning to the previous `JSON.stringify({ ... })` form, but
+ * ~3–5× faster since we avoid the object allocation and JSON serializer for
+ * the fields that are already plain strings.
+ * @param {string} type type of cache
+ * @param {ResolveRequest} request request
+ * @param {boolean} withContext cache with context?
+ * @param {Resolver} resolver resolver instance
+ * @returns {string} cache id
+ */
+function getCacheId(type, request, withContext, resolver) {
+	// TODO use it in the next major release, it is faster
+	// const contextPart = withContext ? JSON.stringify(request.context) : "";
+	// const path = getCachePath(request);
+	// const cacheRequest = getCacheRequest(request, resolver);
+	// return (
+	// 	type +
+	// 	SEP +
+	// 	contextPart +
+	// 	SEP +
+	// 	(path || "") +
+	// 	SEP +
+	// 	(request.query || "") +
+	// 	SEP +
+	// 	(request.fragment || "") +
+	// 	SEP +
+	// 	(cacheRequest || "")
+	// );
+
+	return JSON.stringify({
+		type,
+		context: withContext ? request.context : "",
+		path: getCachePath(request),
+		query: request.query,
+		fragment: request.fragment,
+		request: getCacheRequest(request, resolver),
+	});
+}
+
+module.exports = class UnsafeCachePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate
+	 * @param {Cache} cache cache
+	 * @param {boolean} withContext withContext
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, filterPredicate, cache, withContext, target) {
+		this.source = source;
+		this.filterPredicate = filterPredicate;
+		this.withContext = withContext;
+		this.cache = cache;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
+				if (!this.filterPredicate(request)) {
+					return resolver.doResolve(
+						target,
+						request,
+						null,
+						resolveContext,
+						callback,
+					);
+				}
+				const isYield = typeof resolveContext.yield === "function";
+				const cacheId = getCacheId(
+					isYield ? "yield" : "default",
+					request,
+					this.withContext,
+					resolver,
+				);
+				const cacheEntry = this.cache[cacheId];
+				if (cacheEntry) {
+					if (isYield) {
+						const yield_ =
+							/** @type {ResolveContextYield} */
+							(resolveContext.yield);
+						if (Array.isArray(cacheEntry)) {
+							for (const result of cacheEntry) yield_(result);
+						} else {
+							yield_(cacheEntry);
+						}
+						return callback(null, null);
+					}
+					return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
+				}
+
+				/** @type {ResolveContextYield | undefined} */
+				let yieldFn;
+				/** @type {ResolveContextYield | undefined} */
+				let yield_;
+				/** @type {ResolveRequest[]} */
+				const yieldResult = [];
+				if (isYield) {
+					yieldFn = resolveContext.yield;
+					yield_ = (result) => {
+						yieldResult.push(result);
+					};
+				}
+
+				resolver.doResolve(
+					target,
+					request,
+					null,
+					yield_ ? { ...resolveContext, yield: yield_ } : resolveContext,
+					(err, result) => {
+						if (err) return callback(err);
+						if (isYield) {
+							if (result) yieldResult.push(result);
+							for (const result of yieldResult) {
+								/** @type {ResolveContextYield} */
+								(yieldFn)(result);
+							}
+							this.cache[cacheId] = yieldResult;
+							return callback(null, null);
+						}
+						if (result) return callback(null, (this.cache[cacheId] = result));
+						callback();
+					},
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/UseFilePlugin.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/UseFilePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/UseFilePlugin.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,55 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
+
+module.exports = class UseFilePlugin {
+	/**
+	 * @param {string | ResolveStepHook} source source
+	 * @param {string} filename filename
+	 * @param {string | ResolveStepHook} target target
+	 */
+	constructor(source, filename, target) {
+		this.source = source;
+		this.filename = filename;
+		this.target = target;
+	}
+
+	/**
+	 * @param {Resolver} resolver the resolver
+	 * @returns {void}
+	 */
+	apply(resolver) {
+		const target = resolver.ensureHook(this.target);
+		resolver
+			.getHook(this.source)
+			.tapAsync("UseFilePlugin", (request, resolveContext, callback) => {
+				const filePath = resolver.join(
+					/** @type {string} */ (request.path),
+					this.filename,
+				);
+
+				/** @type {ResolveRequest} */
+				const obj = {
+					...request,
+					path: filePath,
+					relativePath:
+						request.relativePath &&
+						resolver.join(request.relativePath, this.filename),
+				};
+				resolver.doResolve(
+					target,
+					obj,
+					`using path: ${filePath}`,
+					resolveContext,
+					callback,
+				);
+			});
+	}
+};
Index: frontend/node_modules/enhanced-resolve/lib/createInnerContext.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/createInnerContext.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/createInnerContext.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+
+/**
+ * Build the `ResolveContext` passed into the next hook in the chain.
+ *
+ * The caller — `Resolver.doResolve` — runs on every resolve step, so we
+ * want to allocate as little as possible here. Previously the caller
+ * constructed a temporary `{ log, yield, fileDependencies, ... }` literal
+ * and handed it to this helper, which then copied those same fields into
+ * a second fresh object. That's two allocations per step for what is
+ * effectively a struct copy with one mutated field (`stack`) and one
+ * optionally-wrapped field (`log`). Taking the parent context and the
+ * two things we actually want to change (stack, message) as separate
+ * arguments lets us allocate exactly one inner context.
+ * @param {ResolveContext} parent parent resolve context to inherit dependency sets / yield from
+ * @param {ResolveContext["stack"]} stack new stack tip for the nested call
+ * @param {null | string} message log message prefix for this step
+ * @returns {ResolveContext} inner context
+ */
+module.exports = function createInnerContext(parent, stack, message) {
+	const parentLog = parent.log;
+	let innerLog;
+	if (parentLog) {
+		if (message) {
+			let messageReported = false;
+			/**
+			 * @param {string} msg message
+			 */
+			innerLog = (msg) => {
+				if (!messageReported) {
+					parentLog(message);
+					messageReported = true;
+				}
+				parentLog(`  ${msg}`);
+			};
+		} else {
+			innerLog = parentLog;
+		}
+	}
+
+	return {
+		log: innerLog,
+		yield: parent.yield,
+		fileDependencies: parent.fileDependencies,
+		contextDependencies: parent.contextDependencies,
+		missingDependencies: parent.missingDependencies,
+		stack,
+	};
+};
Index: frontend/node_modules/enhanced-resolve/lib/forEachBail.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/forEachBail.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/forEachBail.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+
+/**
+ * @template T
+ * @template Z
+ * @callback Iterator
+ * @param {T} item item
+ * @param {(err?: null | Error, result?: null | Z) => void} callback callback
+ * @param {number} i index
+ * @returns {void}
+ */
+
+/**
+ * @template T
+ * @template Z
+ * @param {T[]} array array
+ * @param {Iterator<T, Z>} iterator iterator
+ * @param {(err?: null | Error, result?: null | Z, i?: number) => void} callback callback after all items are iterated
+ * @returns {void}
+ */
+module.exports = function forEachBail(array, iterator, callback) {
+	if (array.length === 0) return callback();
+
+	let i = 0;
+	const next = () => {
+		/** @type {boolean | undefined} */
+		let loop;
+		iterator(
+			array[i++],
+			(err, result) => {
+				if (err || result !== undefined || i >= array.length) {
+					return callback(err, result, i);
+				}
+				if (loop === false) while (next());
+				loop = true;
+			},
+			i,
+		);
+		if (!loop) loop = false;
+		return loop;
+	};
+	while (next());
+};
Index: frontend/node_modules/enhanced-resolve/lib/getInnerRequest.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/getInnerRequest.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/getInnerRequest.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,41 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { isRelativeRequest } = require("./util/path");
+
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+
+/**
+ * @param {Resolver} resolver resolver
+ * @param {ResolveRequest} request string
+ * @returns {string} inner request
+ */
+module.exports = function getInnerRequest(resolver, request) {
+	if (
+		typeof request.__innerRequest === "string" &&
+		request.__innerRequest_request === request.request &&
+		request.__innerRequest_relativePath === request.relativePath
+	) {
+		return request.__innerRequest;
+	}
+	/** @type {string | undefined} */
+	let innerRequest;
+	if (request.request) {
+		innerRequest = request.request;
+		if (request.relativePath && isRelativeRequest(innerRequest)) {
+			innerRequest = resolver.join(request.relativePath, innerRequest);
+		}
+	} else {
+		innerRequest = request.relativePath;
+	}
+	// eslint-disable-next-line camelcase
+	request.__innerRequest_request = request.request;
+	// eslint-disable-next-line camelcase
+	request.__innerRequest_relativePath = request.relativePath;
+	return (request.__innerRequest = /** @type {string} */ (innerRequest));
+};
Index: frontend/node_modules/enhanced-resolve/lib/getPaths.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/getPaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/getPaths.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {{ paths: string[], segments: string[] }} GetPathsResult */
+
+/**
+ * Walk `path` from tip to root, returning every ancestor directory (plus the
+ * input itself) in `paths`, and each corresponding segment name in `segments`.
+ *
+ * The return value may be shared across callers via `getPathsCached` — treat
+ * it as read-only. Callers that need to mutate (currently only
+ * `SymlinkPlugin`) should `slice()` the arrays locally before writing.
+ * @param {string} path path
+ * @returns {GetPathsResult} paths and segments
+ */
+function getPaths(path) {
+	if (path === "/") return { paths: ["/"], segments: [""] };
+	const parts = path.split(/(.*?[\\/]+)/);
+	const paths = [path];
+	const segments = [parts[parts.length - 1]];
+	let part = parts[parts.length - 1];
+	path = path.slice(0, Math.max(0, path.length - part.length - 1));
+	for (let i = parts.length - 2; i > 2; i -= 2) {
+		paths.push(path);
+		part = parts[i];
+		path = path.slice(0, Math.max(0, path.length - part.length)) || "/";
+		segments.push(part.slice(0, -1));
+	}
+	[, part] = parts;
+	segments.push(part);
+	paths.push(part);
+	return {
+		paths,
+		segments,
+	};
+}
+
+/**
+ * Per-filesystem memoization of `getPaths`. Kept in a standalone WeakMap
+ * rather than being hung off `resolver.pathCache` so that adding this cache
+ * does not change the hidden-class shape of `pathCache` — which is accessed
+ * on the hot path of every resolve as `resolver.pathCache.{join,dirname,
+ * basename}.fn(...)`. CodSpeed caught that shape change as a ~1–2%
+ * instruction-count regression on `cache-predicate`, so we keep pathCache
+ * shape-stable by owning this cache here instead.
+ *
+ * The cache lifetime is tied to the filesystem object (same invariant as
+ * `_pathCacheByFs` in `Resolver.js`): when the user swaps filesystems the
+ * entries become unreachable and get collected.
+ * @type {WeakMap<FileSystem, Map<string, GetPathsResult>>}
+ */
+const _getPathsCacheByFs = new WeakMap();
+
+/**
+ * Memoized `getPaths`. The returned object is shared across callers — do
+ * not mutate the `paths` or `segments` arrays in-place; `slice()` first if
+ * you need a mutable copy.
+ * @param {FileSystem} fileSystem filesystem used as the cache namespace
+ * @param {string} path path
+ * @returns {GetPathsResult} paths and segments
+ */
+function getPathsCached(fileSystem, path) {
+	let cache = _getPathsCacheByFs.get(fileSystem);
+	if (cache === undefined) {
+		cache = new Map();
+		_getPathsCacheByFs.set(fileSystem, cache);
+	} else {
+		const cached = cache.get(path);
+		if (cached !== undefined) return cached;
+	}
+	const result = getPaths(path);
+	cache.set(path, result);
+	return result;
+}
+
+module.exports = getPaths;
+module.exports.getPathsCached = getPathsCached;
Index: frontend/node_modules/enhanced-resolve/lib/index.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,310 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const memoize = require("./util/memoize");
+
+/** @typedef {import("./CachedInputFileSystem").BaseFileSystem} BaseFileSystem */
+/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
+/** @typedef {import("./Resolver")} Resolver */
+/** @typedef {import("./Resolver").Context} Context */
+/** @typedef {import("./Resolver").FileSystem} FileSystem */
+/** @typedef {import("./Resolver").ResolveCallback} ResolveCallback */
+/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
+/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
+/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
+/** @typedef {import("./ResolverFactory").Plugin} Plugin */
+/** @typedef {import("./ResolverFactory").UserResolveOptions} ResolveOptions */
+
+/**
+ * @typedef {{
+ * (context: Context, path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void,
+ * (context: Context, path: string, request: string, callback: ResolveCallback): void,
+ * (path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void,
+ * (path: string, request: string, callback: ResolveCallback): void,
+ * }} ResolveFunctionAsync
+ */
+
+/**
+ * @typedef {{
+ * (context: Context, path: string, request: string, resolveContext?: ResolveContext): string | false,
+ * (path: string, request: string, resolveContext?: ResolveContext): string | false,
+ * }} ResolveFunction
+ */
+
+/**
+ * @typedef {{
+ * (context: Context, path: string, request: string, resolveContext?: ResolveContext): Promise<string | false>,
+ * (path: string, request: string, resolveContext?: ResolveContext): Promise<string | false>,
+ * }} ResolveFunctionPromise
+ */
+
+const getCachedFileSystem = memoize(() => require("./CachedInputFileSystem"));
+
+const getNodeFileSystem = memoize(() => {
+	const fs = require("graceful-fs");
+
+	const CachedInputFileSystem = getCachedFileSystem();
+
+	return new CachedInputFileSystem(fs, 4000);
+});
+const getNodeContext = memoize(() => ({
+	environments: ["node+es3+es5+process+native"],
+}));
+
+const getResolverFactory = memoize(() => require("./ResolverFactory"));
+
+const getAsyncResolver = memoize(() =>
+	getResolverFactory().createResolver({
+		conditionNames: ["node"],
+		extensions: [".js", ".json", ".node"],
+		fileSystem: getNodeFileSystem(),
+	}),
+);
+
+/**
+ * @type {ResolveFunctionAsync}
+ */
+const resolve =
+	/**
+	 * @param {object | string} context context
+	 * @param {string} path path
+	 * @param {string | ResolveContext | ResolveCallback} request request
+	 * @param {ResolveContext | ResolveCallback=} resolveContext resolve context
+	 * @param {ResolveCallback=} callback callback
+	 */
+	(context, path, request, resolveContext, callback) => {
+		if (typeof context === "string") {
+			callback = /** @type {ResolveCallback} */ (resolveContext);
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		if (typeof callback !== "function") {
+			callback = /** @type {ResolveCallback} */ (resolveContext);
+		}
+		getAsyncResolver().resolve(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+			/** @type {ResolveCallback} */ (callback),
+		);
+	};
+
+const getSyncResolver = memoize(() =>
+	getResolverFactory().createResolver({
+		conditionNames: ["node"],
+		extensions: [".js", ".json", ".node"],
+		useSyncFileSystemCalls: true,
+		fileSystem: getNodeFileSystem(),
+	}),
+);
+
+/**
+ * @type {ResolveFunction}
+ */
+const resolveSync =
+	/**
+	 * @param {object | string} context context
+	 * @param {string} path path
+	 * @param {string | ResolveContext | undefined} request request
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {string | false} resolved path
+	 */
+	(context, path, request, resolveContext) => {
+		if (typeof context === "string") {
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		return getSyncResolver().resolveSync(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+		);
+	};
+
+/**
+ * @type {ResolveFunctionPromise}
+ */
+const resolvePromise =
+	/**
+	 * @param {object | string} context context
+	 * @param {string} path path
+	 * @param {string | ResolveContext | undefined} request request
+	 * @param {ResolveContext=} resolveContext resolve context
+	 * @returns {Promise<string | false>} resolved path
+	 */
+	(context, path, request, resolveContext) => {
+		if (typeof context === "string") {
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		return getAsyncResolver().resolvePromise(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+		);
+	};
+
+/** @typedef {Omit<ResolveOptions, "fileSystem"> & Partial<Pick<ResolveOptions, "fileSystem">>} ResolveOptionsOptionalFS */
+
+/**
+ * @param {ResolveOptionsOptionalFS} options Resolver options
+ * @returns {ResolveFunctionAsync} Resolver function
+ */
+function create(options) {
+	const resolver = getResolverFactory().createResolver({
+		fileSystem: getNodeFileSystem(),
+		...options,
+	});
+	/**
+	 * @param {object | string} context Custom context
+	 * @param {string} path Base path
+	 * @param {string | ResolveContext | ResolveCallback} request String to resolve
+	 * @param {ResolveContext | ResolveCallback=} resolveContext Resolve context
+	 * @param {ResolveCallback=} callback Result callback
+	 */
+	return function create(context, path, request, resolveContext, callback) {
+		if (typeof context === "string") {
+			callback = /** @type {ResolveCallback} */ (resolveContext);
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		if (typeof callback !== "function") {
+			callback = /** @type {ResolveCallback} */ (resolveContext);
+		}
+		resolver.resolve(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+			callback,
+		);
+	};
+}
+
+/**
+ * @param {ResolveOptionsOptionalFS} options Resolver options
+ * @returns {ResolveFunction} Resolver function
+ */
+function createSync(options) {
+	const resolver = getResolverFactory().createResolver({
+		useSyncFileSystemCalls: true,
+		fileSystem: getNodeFileSystem(),
+		...options,
+	});
+	/**
+	 * @param {object | string} context custom context
+	 * @param {string} path base path
+	 * @param {string | ResolveContext | undefined} request request to resolve
+	 * @param {ResolveContext=} resolveContext Resolve context
+	 * @returns {string | false} Resolved path or false
+	 */
+	return function createSync(context, path, request, resolveContext) {
+		if (typeof context === "string") {
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		return resolver.resolveSync(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+		);
+	};
+}
+
+/**
+ * @param {ResolveOptionsOptionalFS} options Resolver options
+ * @returns {ResolveFunctionPromise} Resolver function
+ */
+function createPromise(options) {
+	const resolver = getResolverFactory().createResolver({
+		fileSystem: getNodeFileSystem(),
+		...options,
+	});
+	/**
+	 * @param {object | string} context Custom context
+	 * @param {string} path Base path
+	 * @param {string | ResolveContext | undefined} request String to resolve
+	 * @param {ResolveContext=} resolveContext Resolve context
+	 * @returns {Promise<string | false>} resolved path
+	 */
+	return function createPromise(context, path, request, resolveContext) {
+		if (typeof context === "string") {
+			resolveContext = /** @type {ResolveContext} */ (request);
+			request = path;
+			path = context;
+			context = getNodeContext();
+		}
+		return resolver.resolvePromise(
+			context,
+			path,
+			/** @type {string} */ (request),
+			/** @type {ResolveContext} */ (resolveContext),
+		);
+	};
+}
+
+/**
+ * @template A
+ * @template B
+ * @param {A} obj input a
+ * @param {B} exports input b
+ * @returns {A & B} merged
+ */
+const mergeExports = (obj, exports) => {
+	const descriptors = Object.getOwnPropertyDescriptors(exports);
+	Object.defineProperties(obj, descriptors);
+	return /** @type {A & B} */ (Object.freeze(obj));
+};
+
+module.exports = mergeExports(resolve, {
+	get sync() {
+		return resolveSync;
+	},
+	get promise() {
+		return resolvePromise;
+	},
+	create: mergeExports(create, {
+		get sync() {
+			return createSync;
+		},
+		get promise() {
+			return createPromise;
+		},
+	}),
+	get ResolverFactory() {
+		return getResolverFactory();
+	},
+	get CachedInputFileSystem() {
+		return getCachedFileSystem();
+	},
+	get CloneBasenamePlugin() {
+		return require("./CloneBasenamePlugin");
+	},
+	get LogInfoPlugin() {
+		return require("./LogInfoPlugin");
+	},
+	get TsconfigPathsPlugin() {
+		return require("./TsconfigPathsPlugin");
+	},
+	get forEachBail() {
+		return require("./forEachBail");
+	},
+});
Index: frontend/node_modules/enhanced-resolve/lib/util/entrypoints.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/entrypoints.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/entrypoints.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,745 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const { parseIdentifier } = require("./identifier");
+
+/** @typedef {string | (string | ConditionalMapping)[]} DirectMapping */
+/** @typedef {{ [k: string]: MappingValue }} ConditionalMapping */
+/** @typedef {ConditionalMapping | DirectMapping | null} MappingValue */
+/** @typedef {Record<string, MappingValue> | ConditionalMapping | DirectMapping} ExportsField */
+/** @typedef {Record<string, MappingValue>} ImportsField */
+
+/**
+ * Processing exports/imports field
+ * @callback FieldProcessor
+ * @param {string} request request
+ * @param {Set<string>} conditionNames condition names
+ * @returns {[string[], string | null]} resolved paths with used field
+ */
+
+/*
+Example exports field:
+{
+  ".": "./main.js",
+  "./feature": {
+    "browser": "./feature-browser.js",
+    "default": "./feature.js"
+  }
+}
+Terminology:
+
+Enhanced-resolve name keys ("." and "./feature") as exports field keys.
+
+If value is string or string[], mapping is called as a direct mapping
+and value called as a direct export.
+
+If value is key-value object, mapping is called as a conditional mapping
+and value called as a conditional export.
+
+Key in conditional mapping is called condition name.
+
+Conditional mapping nested in another conditional mapping is called nested mapping.
+
+----------
+
+Example imports field:
+{
+  "#a": "./main.js",
+  "#moment": {
+    "browser": "./moment/index.js",
+    "default": "moment"
+  },
+  "#moment/": {
+    "browser": "./moment/",
+    "default": "moment/"
+  }
+}
+Terminology:
+
+Enhanced-resolve name keys ("#a" and "#moment/", "#moment") as imports field keys.
+
+If value is string or string[], mapping is called as a direct mapping
+and value called as a direct export.
+
+If value is key-value object, mapping is called as a conditional mapping
+and value called as a conditional export.
+
+Key in conditional mapping is called condition name.
+
+Conditional mapping nested in another conditional mapping is called nested mapping.
+
+*/
+
+const slashCode = "/".charCodeAt(0);
+const dotCode = ".".charCodeAt(0);
+const hashCode = "#".charCodeAt(0);
+const patternRegEx = /\*/g;
+const DOLLAR_ESCAPE_RE = /\$/g;
+
+/** @typedef {Record<string, MappingValue>} RecordMapping */
+
+/**
+ * Cached `Object.keys()` for objects whose shape does not change after the
+ * first observation — i.e. parsed `package.json` fields and the nested
+ * conditional mappings inside them. `Object.keys` allocates a fresh array
+ * on every call; since `findMatch` / `conditionalMapping` run on every
+ * bare-specifier resolve, the allocation adds up quickly.
+ * @type {WeakMap<RecordMapping, string[]>}
+ */
+const _keysCache = new WeakMap();
+
+/**
+ * @param {RecordMapping} obj object to read keys from
+ * @returns {string[]} cached keys array (DO NOT mutate)
+ */
+function cachedKeys(obj) {
+	let keys = _keysCache.get(obj);
+	if (keys === undefined) {
+		keys = Object.keys(obj);
+		_keysCache.set(obj, keys);
+	}
+	return keys;
+}
+
+/**
+ * Per-key precomputed info used by `findMatch`. Equivalent to what the
+ * previous implementation recomputed inline on every resolve.
+ * @typedef {object} FieldKeyInfo
+ * @property {string} key the original key
+ * @property {number} patternIndex position of the single "*" in the key, or -1 when absent
+ * @property {string} wildcardPrefix substring before "*" (empty when patternIndex === -1)
+ * @property {string} wildcardSuffix substring after "*" (empty when patternIndex === -1)
+ * @property {boolean} isLegacySubpath true when key is a legacy `./foo/`-style folder key with no "*"
+ * @property {boolean} isPattern true when key contains "*"
+ * @property {boolean} isSubpathMapping true when key ends with "/"
+ * @property {boolean} isValidPattern true when key has at most one "*"
+ */
+
+/**
+ * Cached per-field key metadata, keyed by the exports/imports field
+ * object. Computed lazily on first `findMatch` call and reused forever.
+ * Safe because `package.json` fields are immutable JSON values.
+ * @type {WeakMap<RecordMapping, FieldKeyInfo[]>}
+ */
+const _fieldKeyInfoCache = new WeakMap();
+
+/**
+ * @param {ExportsField | ImportsField} field field object
+ * @returns {FieldKeyInfo[]} precomputed per-key info
+ */
+function getFieldKeyInfos(field) {
+	const fieldKey = /** @type {RecordMapping} */ (field);
+	let infos = _fieldKeyInfoCache.get(fieldKey);
+	if (infos !== undefined) return infos;
+	const keys = Object.getOwnPropertyNames(field);
+	infos = Array.from({ length: keys.length });
+	for (let i = 0; i < keys.length; i++) {
+		const key = keys[i];
+		const patternIndex = key.indexOf("*");
+		// `isValidPattern` is true when the key has at most one `*`. Searching
+		// from `patternIndex + 1` stops as soon as a second `*` is found, so
+		// we avoid the full-string scan that `lastIndexOf` would do — and the
+		// single-star common case finishes in one pass.
+		const isValidPattern =
+			patternIndex === -1 || !key.includes("*", patternIndex + 1);
+		const keyLen = key.length;
+		const endsWithSlash =
+			keyLen > 0 && key.charCodeAt(keyLen - 1) === slashCode;
+		infos[i] = {
+			key,
+			patternIndex,
+			wildcardPrefix: patternIndex === -1 ? "" : key.slice(0, patternIndex),
+			wildcardSuffix: patternIndex === -1 ? "" : key.slice(patternIndex + 1),
+			isLegacySubpath: patternIndex === -1 && endsWithSlash,
+			isPattern: patternIndex !== -1,
+			isSubpathMapping: endsWithSlash,
+			isValidPattern,
+		};
+	}
+	_fieldKeyInfoCache.set(fieldKey, infos);
+	return infos;
+}
+
+/**
+ * @param {string} a first string
+ * @param {string} b second string
+ * @returns {number} compare result
+ */
+function patternKeyCompare(a, b) {
+	const aPatternIndex = a.indexOf("*");
+	const bPatternIndex = b.indexOf("*");
+	const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
+	const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
+
+	if (baseLenA > baseLenB) return -1;
+	if (baseLenB > baseLenA) return 1;
+	if (aPatternIndex === -1) return 1;
+	if (bPatternIndex === -1) return -1;
+	if (a.length > b.length) return -1;
+	if (b.length > a.length) return 1;
+
+	return 0;
+}
+
+/** @typedef {[MappingValue, string, boolean, boolean, string] | null} MatchTuple */
+
+/**
+ * Per-field memoization of `findMatch(request, field)`. For a given field
+ * the result depends only on the `request` string (it does NOT depend on
+ * `conditionNames` — that's applied separately by `conditionalMapping`),
+ * so we can cache the tuple keyed by request.
+ *
+ * Typical build traffic runs the same request through the resolver
+ * repeatedly (same import re-resolved from different source files, module
+ * graph traversals that revisit a package, etc.), and every one of those
+ * hits walks the same key list and allocates the same tuple. Caching the
+ * tuple turns the second-and-onward call into a single Map lookup.
+ *
+ * Keyed on the field object via a module-level `WeakMap`, so the cache
+ * is freed automatically when the owning description file is GC'd.
+ * @type {WeakMap<RecordMapping, Map<string, MatchTuple>>}
+ */
+const _findMatchCache = new WeakMap();
+
+/**
+ * @param {string} request request
+ * @param {ExportsField | ImportsField} field exports or import field
+ * @returns {MatchTuple} match result (uncached)
+ */
+function computeFindMatch(request, field) {
+	const requestLen = request.length;
+	const requestEndsWithSlash =
+		requestLen > 0 && request.charCodeAt(requestLen - 1) === slashCode;
+	const requestHasStar = request.includes("*");
+
+	if (
+		!requestHasStar &&
+		!requestEndsWithSlash &&
+		Object.prototype.hasOwnProperty.call(field, request)
+	) {
+		const target = /** @type {{ [k: string]: MappingValue }} */ (field)[
+			request
+		];
+
+		return [target, "", false, false, request];
+	}
+
+	/** @type {string} */
+	let bestMatch = "";
+	/** @type {FieldKeyInfo | null} */
+	let bestMatchInfo = null;
+	/** @type {string | undefined} */
+	let bestMatchSubpath;
+
+	const infos = getFieldKeyInfos(field);
+
+	for (let i = 0; i < infos.length; i++) {
+		const info = infos[i];
+		const { key, patternIndex } = info;
+
+		if (patternIndex !== -1) {
+			if (
+				!info.isValidPattern ||
+				!request.startsWith(info.wildcardPrefix) ||
+				requestLen < key.length ||
+				!request.endsWith(info.wildcardSuffix) ||
+				patternKeyCompare(bestMatch, key) !== 1
+			) {
+				continue;
+			}
+			bestMatch = key;
+			bestMatchInfo = info;
+			bestMatchSubpath = request.slice(
+				patternIndex,
+				requestLen - info.wildcardSuffix.length,
+			);
+		} else if (
+			info.isLegacySubpath &&
+			request.startsWith(key) &&
+			patternKeyCompare(bestMatch, key) === 1
+		) {
+			bestMatch = key;
+			bestMatchInfo = info;
+			bestMatchSubpath = request.slice(key.length);
+		}
+	}
+
+	if (bestMatch === "") return null;
+
+	const target =
+		/** @type {{ [k: string]: MappingValue }} */
+		(field)[bestMatch];
+
+	return [
+		target,
+		/** @type {string} */ (bestMatchSubpath),
+		/** @type {FieldKeyInfo} */ (bestMatchInfo).isSubpathMapping,
+		/** @type {FieldKeyInfo} */ (bestMatchInfo).isPattern,
+		bestMatch,
+	];
+}
+
+/**
+ * Trying to match request to field
+ * @param {string} request request
+ * @param {ExportsField | ImportsField} field exports or import field
+ * @returns {MatchTuple} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings
+ */
+function findMatch(request, field) {
+	const fieldKey = /** @type {RecordMapping} */ (field);
+	let perRequest = _findMatchCache.get(fieldKey);
+	if (perRequest === undefined) {
+		perRequest = new Map();
+		_findMatchCache.set(fieldKey, perRequest);
+	} else {
+		// `computeFindMatch` only ever returns `MatchTuple | null` — never
+		// `undefined` — and `Map.set(k, null)` then `Map.get(k)` returns
+		// `null`, not `undefined`. So `get(...) === undefined` already
+		// unambiguously means "not cached yet"; one Map lookup is enough,
+		// no follow-up `has` needed to disambiguate "cached null".
+		const cached = perRequest.get(request);
+		if (cached !== undefined) return cached;
+	}
+
+	const result = computeFindMatch(request, field);
+	perRequest.set(request, result);
+	return result;
+}
+
+/**
+ * Sentinel stored in the conditional-mapping cache for inputs whose walk
+ * returns `null` ("no condition matched"). Using a non-null marker lets the
+ * cache-hit path be a single `WeakMap.get()` — we distinguish
+ * "cached null" from "not cached yet" without a second `has` call.
+ */
+const NULL_RESULT = Symbol("NULL_RESULT");
+
+/**
+ * Memoization of `conditionalMapping(mapping, conditionNames)`. The result
+ * depends only on the mapping object (immutable — owned by a parsed
+ * `package.json`) and the `conditionNames` Set (owned by the resolver's
+ * options and stable for its lifetime), so it is safe to cache per (mapping,
+ * conditionNames) pair.
+ *
+ * A conditional `exports` entry that appears inside a `directMapping` array
+ * (the common `"browser": [...fallback list...]` shape, plus nested
+ * conditions) gets walked on every resolve that traverses the parent entry.
+ * Without this cache each of those walks re-reads `Object.keys` on the
+ * mapping and re-visits every condition until one matches, even though the
+ * inputs are identical.
+ *
+ * Outer key is the conditional mapping itself; inner key is the condition
+ * Set. Both are object references, so WeakMap-of-WeakMap lets both levels
+ * be collected automatically when the description file or resolver go away.
+ * @type {WeakMap<ConditionalMapping, WeakMap<Set<string>, DirectMapping | typeof NULL_RESULT>>}
+ */
+const _conditionalMappingCache = new WeakMap();
+
+/**
+ * @param {ConditionalMapping} conditionalMapping_ conditional mapping
+ * @param {Set<string>} conditionNames condition names
+ * @returns {DirectMapping | null} direct mapping if found (uncached)
+ */
+function computeConditionalMapping(conditionalMapping_, conditionNames) {
+	/** @type {[ConditionalMapping, string[], number][]} */
+	const lookup = [[conditionalMapping_, cachedKeys(conditionalMapping_), 0]];
+
+	loop: while (lookup.length > 0) {
+		const top = lookup[lookup.length - 1];
+		const [mapping, conditions, j] = top;
+
+		for (let i = j; i < conditions.length; i++) {
+			const condition = conditions[i];
+
+			if (condition === "default" || conditionNames.has(condition)) {
+				const innerMapping = mapping[condition];
+				if (
+					innerMapping !== null &&
+					typeof innerMapping === "object" &&
+					!Array.isArray(innerMapping)
+				) {
+					const nested = /** @type {ConditionalMapping} */ (innerMapping);
+					top[2] = i + 1;
+					lookup.push([nested, cachedKeys(nested), 0]);
+					continue loop;
+				}
+
+				return /** @type {DirectMapping} */ (innerMapping);
+			}
+		}
+
+		lookup.pop();
+	}
+
+	return null;
+}
+
+/**
+ * @param {ConditionalMapping} conditionalMapping_ conditional mapping
+ * @param {Set<string>} conditionNames condition names
+ * @returns {DirectMapping | null} direct mapping if found
+ */
+function conditionalMapping(conditionalMapping_, conditionNames) {
+	let perSet = _conditionalMappingCache.get(conditionalMapping_);
+	if (perSet !== undefined) {
+		const cached = perSet.get(conditionNames);
+		if (cached !== undefined) {
+			return cached === NULL_RESULT
+				? null
+				: /** @type {DirectMapping} */ (cached);
+		}
+	} else {
+		perSet = new WeakMap();
+		_conditionalMappingCache.set(conditionalMapping_, perSet);
+	}
+	const result = computeConditionalMapping(conditionalMapping_, conditionNames);
+	perSet.set(conditionNames, result === null ? NULL_RESULT : result);
+	return result;
+}
+
+/**
+ * @param {string | undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
+ * @param {boolean} isPattern true, if mapping is a pattern (contains "*")
+ * @param {boolean} isSubpathMapping true, for subpath mappings
+ * @param {string} mappingTarget direct export
+ * @param {(d: string, f: boolean) => void} assert asserting direct value
+ * @returns {string} mapping result
+ */
+function targetMapping(
+	remainingRequest,
+	isPattern,
+	isSubpathMapping,
+	mappingTarget,
+	assert,
+) {
+	if (remainingRequest === undefined) {
+		assert(mappingTarget, false);
+
+		return mappingTarget;
+	}
+
+	if (isSubpathMapping) {
+		assert(mappingTarget, true);
+
+		return mappingTarget + remainingRequest;
+	}
+
+	assert(mappingTarget, false);
+
+	let result = mappingTarget;
+
+	if (isPattern) {
+		const escapedRemainder = remainingRequest.includes("$")
+			? remainingRequest.replace(DOLLAR_ESCAPE_RE, "$$")
+			: remainingRequest;
+		result = result.replace(patternRegEx, escapedRemainder);
+	}
+
+	return result;
+}
+
+/**
+ * @param {string | undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
+ * @param {boolean} isPattern true, if mapping is a pattern (contains "*")
+ * @param {boolean} isSubpathMapping true, for subpath mappings
+ * @param {DirectMapping | null} mappingTarget direct export
+ * @param {Set<string>} conditionNames condition names
+ * @param {(d: string, f: boolean) => void} assert asserting direct value
+ * @returns {string[]} mapping result
+ */
+function directMapping(
+	remainingRequest,
+	isPattern,
+	isSubpathMapping,
+	mappingTarget,
+	conditionNames,
+	assert,
+) {
+	if (mappingTarget === null) return [];
+
+	if (typeof mappingTarget === "string") {
+		return [
+			targetMapping(
+				remainingRequest,
+				isPattern,
+				isSubpathMapping,
+				mappingTarget,
+				assert,
+			),
+		];
+	}
+
+	/** @type {string[]} */
+	const targets = [];
+
+	for (let i = 0, len = mappingTarget.length; i < len; i++) {
+		const exp = mappingTarget[i];
+		if (typeof exp === "string") {
+			targets.push(
+				targetMapping(
+					remainingRequest,
+					isPattern,
+					isSubpathMapping,
+					exp,
+					assert,
+				),
+			);
+			continue;
+		}
+
+		const mapping = conditionalMapping(exp, conditionNames);
+		if (!mapping) continue;
+		const innerExports = directMapping(
+			remainingRequest,
+			isPattern,
+			isSubpathMapping,
+			mapping,
+			conditionNames,
+			assert,
+		);
+		for (let j = 0, innerLen = innerExports.length; j < innerLen; j++) {
+			targets.push(innerExports[j]);
+		}
+	}
+
+	return targets;
+}
+
+/** @type {[string[], null]} */
+const EMPTY_NO_MATCH = /** @type {[string[], null]} */ ([[], null]);
+
+/**
+ * @param {ExportsField | ImportsField} field root
+ * @param {(s: string) => string} normalizeRequest Normalize request, for `imports` field it adds `#`, for `exports` field it adds `.` or `./`
+ * @param {(s: string) => string} assertRequest assertRequest
+ * @param {(s: string, f: boolean) => void} assertTarget assertTarget
+ * @returns {FieldProcessor} field processor
+ */
+function createFieldProcessor(
+	field,
+	normalizeRequest,
+	assertRequest,
+	assertTarget,
+) {
+	return function fieldProcessor(request, conditionNames) {
+		const match = findMatch(normalizeRequest(assertRequest(request)), field);
+
+		if (match === null) return EMPTY_NO_MATCH;
+
+		const [mapping, remainingRequest, isSubpathMapping, isPattern, usedField] =
+			match;
+
+		/** @type {DirectMapping | null} */
+		let direct;
+		if (
+			mapping !== null &&
+			typeof mapping === "object" &&
+			!Array.isArray(mapping)
+		) {
+			direct = conditionalMapping(
+				/** @type {ConditionalMapping} */ (mapping),
+				conditionNames,
+			);
+			if (direct === null) return EMPTY_NO_MATCH;
+		} else {
+			direct = /** @type {DirectMapping} */ (mapping);
+		}
+
+		return [
+			directMapping(
+				remainingRequest,
+				isPattern,
+				isSubpathMapping,
+				direct,
+				conditionNames,
+				assertTarget,
+			),
+			usedField,
+		];
+	};
+}
+
+/**
+ * @param {string} request request
+ * @returns {string} updated request
+ */
+function assertExportsFieldRequest(request) {
+	if (request.charCodeAt(0) !== dotCode) {
+		throw new Error('Request should be relative path and start with "."');
+	}
+	if (request.length === 1) return "";
+	if (request.charCodeAt(1) !== slashCode) {
+		throw new Error('Request should be relative path and start with "./"');
+	}
+	if (request.charCodeAt(request.length - 1) === slashCode) {
+		throw new Error("Only requesting file allowed");
+	}
+
+	return request.slice(2);
+}
+
+/**
+ * @param {ExportsField} field exports field
+ * @returns {ExportsField} normalized exports field
+ */
+function buildExportsField(field) {
+	// handle syntax sugar, if exports field is direct mapping for "."
+	if (typeof field === "string" || Array.isArray(field)) {
+		return { ".": field };
+	}
+
+	const keys = Object.keys(field);
+
+	for (let i = 0; i < keys.length; i++) {
+		const key = keys[i];
+
+		if (key.charCodeAt(0) !== dotCode) {
+			// handle syntax sugar, if exports field is conditional mapping for "."
+			if (i === 0) {
+				while (i < keys.length) {
+					const charCode = keys[i].charCodeAt(0);
+					if (charCode === dotCode || charCode === slashCode) {
+						throw new Error(
+							`Exports field key should be relative path and start with "." (key: ${JSON.stringify(
+								key,
+							)})`,
+						);
+					}
+					i++;
+				}
+
+				return { ".": field };
+			}
+
+			throw new Error(
+				`Exports field key should be relative path and start with "." (key: ${JSON.stringify(
+					key,
+				)})`,
+			);
+		}
+
+		if (key.length === 1) {
+			continue;
+		}
+
+		if (key.charCodeAt(1) !== slashCode) {
+			throw new Error(
+				`Exports field key should be relative path and start with "./" (key: ${JSON.stringify(
+					key,
+				)})`,
+			);
+		}
+	}
+
+	return field;
+}
+
+/**
+ * @param {string} exp export target
+ * @param {boolean} expectFolder is folder expected
+ */
+function assertExportTarget(exp, expectFolder) {
+	const parsedIdentifier = parseIdentifier(exp);
+
+	if (!parsedIdentifier) {
+		return;
+	}
+
+	const [relativePath] = parsedIdentifier;
+	const isFolder =
+		relativePath.charCodeAt(relativePath.length - 1) === slashCode;
+
+	if (isFolder !== expectFolder) {
+		throw new Error(
+			expectFolder
+				? `Expecting folder to folder mapping. ${JSON.stringify(
+						exp,
+					)} should end with "/"`
+				: `Expecting file to file mapping. ${JSON.stringify(
+						exp,
+					)} should not end with "/"`,
+		);
+	}
+}
+
+/**
+ * @param {ExportsField} exportsField the exports field
+ * @returns {FieldProcessor} process callback
+ */
+module.exports.processExportsField = function processExportsField(
+	exportsField,
+) {
+	return createFieldProcessor(
+		buildExportsField(exportsField),
+		(request) => (request.length === 0 ? "." : `./${request}`),
+		assertExportsFieldRequest,
+		assertExportTarget,
+	);
+};
+
+/**
+ * @param {string} request request
+ * @returns {string} updated request
+ */
+function assertImportsFieldRequest(request) {
+	if (request.charCodeAt(0) !== hashCode) {
+		throw new Error('Request should start with "#"');
+	}
+	if (request.length === 1) {
+		throw new Error("Request should have at least 2 characters");
+	}
+	// Note: #/ patterns are now allowed per Node.js PR #60864
+	// https://github.com/nodejs/node/pull/60864
+	if (request.charCodeAt(request.length - 1) === slashCode) {
+		throw new Error("Only requesting file allowed");
+	}
+
+	return request.slice(1);
+}
+
+/**
+ * @param {string} imp import target
+ * @param {boolean} expectFolder is folder expected
+ */
+function assertImportTarget(imp, expectFolder) {
+	const parsedIdentifier = parseIdentifier(imp);
+
+	if (!parsedIdentifier) {
+		return;
+	}
+
+	const [relativePath] = parsedIdentifier;
+	const isFolder =
+		relativePath.charCodeAt(relativePath.length - 1) === slashCode;
+
+	if (isFolder !== expectFolder) {
+		throw new Error(
+			expectFolder
+				? `Expecting folder to folder mapping. ${JSON.stringify(
+						imp,
+					)} should end with "/"`
+				: `Expecting file to file mapping. ${JSON.stringify(
+						imp,
+					)} should not end with "/"`,
+		);
+	}
+}
+
+/**
+ * @param {ImportsField} importsField the exports field
+ * @returns {FieldProcessor} process callback
+ */
+module.exports.processImportsField = function processImportsField(
+	importsField,
+) {
+	return createFieldProcessor(
+		importsField,
+		(request) => `#${request}`,
+		assertImportsFieldRequest,
+		assertImportTarget,
+	);
+};
Index: frontend/node_modules/enhanced-resolve/lib/util/fs.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/fs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/fs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,75 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+*/
+
+"use strict";
+
+const stripJsonComments = require("./strip-json-comments");
+
+/** @typedef {import("../Resolver").FileSystem} FileSystem */
+/** @typedef {import("../Resolver").JsonObject} JsonObject */
+
+/**
+ * @typedef {object} ReadJsonOptions
+ * @property {boolean=} stripComments Whether to strip JSONC comments
+ */
+
+/** @type {WeakMap<Buffer, JsonObject>} */
+const _stripCommentsCache = new WeakMap();
+
+/**
+ * Read and parse JSON file (supports JSONC with comments).
+ * Callback-based so a synchronous `fileSystem` stays synchronous all the
+ * way through — Promise wrapping would defer resolution by a Promise tick
+ * and break `resolveSync` when `tsconfig` is used together with
+ * `useSyncFileSystemCalls: true`.
+ * @param {FileSystem} fileSystem the file system
+ * @param {string} jsonFilePath absolute path to JSON file
+ * @param {ReadJsonOptions} options Options
+ * @param {(err: NodeJS.ErrnoException | Error | null, content?: JsonObject) => void} callback callback
+ * @returns {void}
+ */
+function readJson(fileSystem, jsonFilePath, options, callback) {
+	const { stripComments = false } = options;
+	const { readJson: fsReadJson } = fileSystem;
+	if (fsReadJson && !stripComments) {
+		fsReadJson(jsonFilePath, (err, content) => {
+			if (err) return callback(err);
+			callback(null, /** @type {JsonObject} */ (content));
+		});
+		return;
+	}
+
+	fileSystem.readFile(jsonFilePath, (err, data) => {
+		if (err) return callback(err);
+		const buf = /** @type {Buffer} */ (data);
+
+		if (stripComments) {
+			const cached = _stripCommentsCache.get(buf);
+			if (cached !== undefined) return callback(null, cached);
+		}
+
+		let result;
+		try {
+			const jsonText = buf.toString();
+			const jsonWithoutComments = stripComments
+				? stripJsonComments(jsonText, {
+						trailingCommas: true,
+						whitespace: true,
+					})
+				: jsonText;
+			result = JSON.parse(jsonWithoutComments);
+		} catch (parseErr) {
+			return callback(/** @type {Error} */ (parseErr));
+		}
+
+		if (stripComments) {
+			_stripCommentsCache.set(buf, result);
+		}
+
+		callback(null, result);
+	});
+}
+
+module.exports.readJson = readJson;
Index: frontend/node_modules/enhanced-resolve/lib/util/identifier.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/identifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/identifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,103 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const memorize = require("./memoize");
+
+const getUrl = memorize(() => require("url"));
+
+const PATH_QUERY_FRAGMENT_REGEXP =
+	/^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
+const ZERO_ESCAPE_REGEXP = /\0(.)/g;
+const FILE_REG_EXP = /file:/i;
+
+/**
+ * Index past a DOS device path prefix (`\\?\…` or `\\.\…`), or 0. Kept
+ * out of `parseIdentifier` on purpose: inlining it back bloats the caller
+ * beyond the size where V8's interpreter and JIT both handle it well
+ * (the cause of the description-files-multi CodSpeed regression).
+ * @param {string} identifier identifier known to start with `\`
+ * @returns {number} 4 if identifier starts with a DOS device prefix, else 0
+ */
+function dosPrefixEnd(identifier) {
+	if (
+		identifier.length >= 4 &&
+		identifier.charCodeAt(1) === 92 &&
+		identifier.charCodeAt(3) === 92
+	) {
+		const c2 = identifier.charCodeAt(2);
+		if (c2 === 63 || c2 === 46) return 4;
+	}
+	return 0;
+}
+
+/**
+ * @param {string} identifier identifier
+ * @returns {[string, string, string] | null} parsed identifier
+ */
+function parseIdentifier(identifier) {
+	if (!identifier) {
+		return null;
+	}
+
+	if (FILE_REG_EXP.test(identifier)) {
+		identifier = getUrl().fileURLToPath(identifier);
+	}
+
+	const firstEscape = identifier.indexOf("\0");
+
+	// Handle `\0`
+	if (firstEscape !== -1) {
+		const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);
+
+		if (!match) return null;
+
+		return [
+			match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
+			match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
+			match[3] || "",
+		];
+	}
+
+	// Fast path for inputs that don't use \0 escaping. DOS device paths
+	// (`\\?\…`, `\\.\…`) embed a literal `?` / `.` that must not be read
+	// as a query separator; skip past the prefix when the input actually
+	// starts with `\`. Gate is a single char-code compare so this function
+	// stays inside V8's inline budget for its hot callers (resolver parse).
+	const scanStart =
+		identifier.charCodeAt(0) === 92 ? dosPrefixEnd(identifier) : 0;
+	const queryStart = identifier.indexOf("?", scanStart);
+	// Start at index 1 (or past a DOS prefix) to ignore a possible leading hash.
+	const fragmentStart = identifier.indexOf("#", scanStart || 1);
+
+	if (fragmentStart < 0) {
+		if (queryStart < 0) {
+			// No fragment, no query
+			return [identifier, "", ""];
+		}
+
+		// Query, no fragment
+		return [identifier.slice(0, queryStart), identifier.slice(queryStart), ""];
+	}
+
+	if (queryStart < 0 || fragmentStart < queryStart) {
+		// Fragment, no query
+		return [
+			identifier.slice(0, fragmentStart),
+			"",
+			identifier.slice(fragmentStart),
+		];
+	}
+
+	// Query and fragment
+	return [
+		identifier.slice(0, queryStart),
+		identifier.slice(queryStart, fragmentStart),
+		identifier.slice(fragmentStart),
+	];
+}
+
+module.exports.parseIdentifier = parseIdentifier;
Index: frontend/node_modules/enhanced-resolve/lib/util/memoize.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/memoize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/memoize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,37 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+/**
+ * @template T
+ * @typedef {() => T} FunctionReturning
+ */
+
+/**
+ * @template T
+ * @param {FunctionReturning<T>} fn memorized function
+ * @returns {FunctionReturning<T>} new function
+ */
+const memoize = (fn) => {
+	let cache = false;
+	/** @type {T | undefined} */
+	let result;
+	return () => {
+		if (cache) {
+			return /** @type {T} */ (result);
+		}
+
+		result = fn();
+		cache = true;
+		// Allow to clean up memory for fn
+		// and all dependent resources
+		/** @type {FunctionReturning<T> | undefined} */
+		(fn) = undefined;
+		return /** @type {T} */ (result);
+	};
+};
+
+module.exports = memoize;
Index: frontend/node_modules/enhanced-resolve/lib/util/module-browser.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/module-browser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/module-browser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+module.exports = {};
Index: frontend/node_modules/enhanced-resolve/lib/util/path.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,344 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const path = require("path");
+
+const CHAR_HASH = "#".charCodeAt(0);
+const CHAR_SLASH = "/".charCodeAt(0);
+const CHAR_BACKSLASH = "\\".charCodeAt(0);
+const CHAR_A = "A".charCodeAt(0);
+const CHAR_Z = "Z".charCodeAt(0);
+const CHAR_LOWER_A = "a".charCodeAt(0);
+const CHAR_LOWER_Z = "z".charCodeAt(0);
+const CHAR_DOT = ".".charCodeAt(0);
+const CHAR_COLON = ":".charCodeAt(0);
+const CHAR_QUESTION = "?".charCodeAt(0);
+
+const posixNormalize = path.posix.normalize;
+const winNormalize = path.win32.normalize;
+
+/**
+ * @enum {number}
+ */
+const PathType = Object.freeze({
+	Empty: 0,
+	Normal: 1,
+	Relative: 2,
+	AbsoluteWin: 3,
+	AbsolutePosix: 4,
+	Internal: 5,
+});
+
+const deprecatedInvalidSegmentRegEx =
+	/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
+
+const invalidSegmentRegEx =
+	/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
+
+/**
+ * @param {string} maybePath a path known to start with `\\`
+ * @returns {PathType} AbsoluteWin for `\\?\…` / `\\.\…`, otherwise Normal
+ */
+const getDosDeviceType = (maybePath) => {
+	if (maybePath.length >= 4 && maybePath.charCodeAt(3) === CHAR_BACKSLASH) {
+		const c2 = maybePath.charCodeAt(2);
+		if (c2 === CHAR_QUESTION || c2 === CHAR_DOT) {
+			return PathType.AbsoluteWin;
+		}
+	}
+	return PathType.Normal;
+};
+
+/**
+ * @param {string} maybePath a path
+ * @returns {PathType} type of path
+ */
+const getType = (maybePath) => {
+	switch (maybePath.length) {
+		case 0:
+			return PathType.Empty;
+		case 1: {
+			const c0 = maybePath.charCodeAt(0);
+			switch (c0) {
+				case CHAR_DOT:
+					return PathType.Relative;
+				case CHAR_SLASH:
+					return PathType.AbsolutePosix;
+				case CHAR_HASH:
+					return PathType.Internal;
+			}
+			return PathType.Normal;
+		}
+		case 2: {
+			const c0 = maybePath.charCodeAt(0);
+			switch (c0) {
+				case CHAR_DOT: {
+					const c1 = maybePath.charCodeAt(1);
+					switch (c1) {
+						case CHAR_DOT:
+						case CHAR_SLASH:
+							return PathType.Relative;
+					}
+					return PathType.Normal;
+				}
+				case CHAR_SLASH:
+					return PathType.AbsolutePosix;
+				case CHAR_HASH:
+					return PathType.Internal;
+			}
+			const c1 = maybePath.charCodeAt(1);
+			if (
+				c1 === CHAR_COLON &&
+				((c0 >= CHAR_A && c0 <= CHAR_Z) ||
+					(c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
+			) {
+				return PathType.AbsoluteWin;
+			}
+			return PathType.Normal;
+		}
+	}
+	const c0 = maybePath.charCodeAt(0);
+	switch (c0) {
+		case CHAR_DOT: {
+			const c1 = maybePath.charCodeAt(1);
+			switch (c1) {
+				case CHAR_SLASH:
+					return PathType.Relative;
+				case CHAR_DOT: {
+					const c2 = maybePath.charCodeAt(2);
+					if (c2 === CHAR_SLASH) return PathType.Relative;
+					return PathType.Normal;
+				}
+			}
+			return PathType.Normal;
+		}
+		case CHAR_SLASH:
+			return PathType.AbsolutePosix;
+		case CHAR_HASH:
+			return PathType.Internal;
+	}
+	const c1 = maybePath.charCodeAt(1);
+	if (c1 === CHAR_COLON) {
+		const c2 = maybePath.charCodeAt(2);
+		if (
+			(c2 === CHAR_BACKSLASH || c2 === CHAR_SLASH) &&
+			((c0 >= CHAR_A && c0 <= CHAR_Z) ||
+				(c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
+		) {
+			return PathType.AbsoluteWin;
+		}
+	}
+	// DOS device paths (`\\?\…`, `\\.\…`) are handled in a cold helper so
+	// this function stays small — inlining the full check here regressed
+	// `description-files-multi` under `--no-opt` interpretation. Here we
+	// only pay the two-byte gate for non-DOS inputs.
+	if (c0 === CHAR_BACKSLASH && c1 === CHAR_BACKSLASH) {
+		return getDosDeviceType(maybePath);
+	}
+	return PathType.Normal;
+};
+
+/**
+ * @param {string} maybePath a path
+ * @returns {string} the normalized path
+ */
+const normalize = (maybePath) => {
+	switch (getType(maybePath)) {
+		case PathType.Empty:
+			return maybePath;
+		case PathType.AbsoluteWin:
+			return winNormalize(maybePath);
+		case PathType.Relative: {
+			const r = posixNormalize(maybePath);
+			return getType(r) === PathType.Relative ? r : `./${r}`;
+		}
+	}
+	return posixNormalize(maybePath);
+};
+
+/**
+ * @param {string} rootPath the root path
+ * @param {string | undefined} request the request path
+ * @returns {string} the joined path
+ */
+const join = (rootPath, request) => {
+	if (!request) return normalize(rootPath);
+	const requestType = getType(request);
+	switch (requestType) {
+		case PathType.AbsolutePosix:
+			return posixNormalize(request);
+		case PathType.AbsoluteWin:
+			return winNormalize(request);
+	}
+	switch (getType(rootPath)) {
+		case PathType.Normal:
+		case PathType.Relative:
+		case PathType.AbsolutePosix:
+			return posixNormalize(`${rootPath}/${request}`);
+		case PathType.AbsoluteWin:
+			return winNormalize(`${rootPath}\\${request}`);
+	}
+	switch (requestType) {
+		case PathType.Empty:
+			return rootPath;
+		case PathType.Relative: {
+			const r = posixNormalize(rootPath);
+			return getType(r) === PathType.Relative ? r : `./${r}`;
+		}
+	}
+	return posixNormalize(rootPath);
+};
+
+/**
+ * @param {string} maybePath a path
+ * @returns {string} the directory name
+ */
+const dirname = (maybePath) => {
+	switch (getType(maybePath)) {
+		case PathType.AbsoluteWin:
+			return path.win32.dirname(maybePath);
+	}
+	return path.posix.dirname(maybePath);
+};
+
+/** @typedef {{ fn: (rootPath: string, request: string) => string, cache: Map<string, Map<string, string | undefined>> }} CachedJoin */
+
+/**
+ * @returns {CachedJoin} cached join
+ */
+const createCachedJoin = () => {
+	/** @type {CachedJoin["cache"]} */
+	const cache = new Map();
+	/** @type {CachedJoin["fn"]} */
+	const fn = (rootPath, request) => {
+		/** @type {string | undefined} */
+		let cacheEntry;
+		let inner = cache.get(rootPath);
+		if (inner === undefined) {
+			cache.set(rootPath, (inner = new Map()));
+		} else {
+			cacheEntry = inner.get(request);
+			if (cacheEntry !== undefined) return cacheEntry;
+		}
+		cacheEntry = join(rootPath, request);
+		inner.set(request, cacheEntry);
+		return cacheEntry;
+	};
+	return { fn, cache };
+};
+
+/** @typedef {{ fn: (maybePath: string) => string, cache: Map<string, string> }} CachedDirname */
+
+/**
+ * @returns {CachedDirname} cached dirname
+ */
+const createCachedDirname = () => {
+	/** @type {CachedDirname["cache"]} */
+	const cache = new Map();
+	/** @type {CachedDirname["fn"]} */
+	const fn = (maybePath) => {
+		const cacheEntry = cache.get(maybePath);
+		if (cacheEntry !== undefined) return cacheEntry;
+		const result = dirname(maybePath);
+		cache.set(maybePath, result);
+		return result;
+	};
+	return { fn, cache };
+};
+
+/** @typedef {{ fn: (maybePath: string, suffix?: string) => string, cache: Map<string, Map<string | undefined, string | undefined>> }} CachedBasename */
+
+/**
+ * @returns {CachedBasename} cached basename
+ */
+const createCachedBasename = () => {
+	/** @type {CachedBasename["cache"]} */
+	const cache = new Map();
+	/** @type {CachedBasename["fn"]} */
+	const fn = (maybePath, suffix) => {
+		/** @type {string | undefined} */
+		let cacheEntry;
+		let inner = cache.get(maybePath);
+		if (inner === undefined) {
+			cache.set(maybePath, (inner = new Map()));
+		} else {
+			cacheEntry = inner.get(suffix);
+			if (cacheEntry !== undefined) return cacheEntry;
+		}
+		cacheEntry = path.basename(maybePath, suffix);
+		inner.set(suffix, cacheEntry);
+		return cacheEntry;
+	};
+	return { fn, cache };
+};
+
+/**
+ * Whether `request` is a relative request — i.e. matches `^\.\.?(?:\/|$)`.
+ *
+ * This is called on every `doResolve` via `UnsafeCachePlugin` and
+ * `getInnerRequest`, so the char-code form is meaningfully faster than the
+ * equivalent regex test: no regex state machine, no string object churn.
+ * @param {string} request request string
+ * @returns {boolean} true if request is relative
+ */
+const isRelativeRequest = (request) => {
+	const len = request.length;
+	if (len === 0 || request.charCodeAt(0) !== CHAR_DOT) return false;
+	if (len === 1) return true; // "."
+	const c1 = request.charCodeAt(1);
+	if (c1 === CHAR_SLASH) return true; // "./..."
+	if (c1 !== CHAR_DOT) return false; // ".x..."
+	if (len === 2) return true; // ".."
+	return request.charCodeAt(2) === CHAR_SLASH; // "../..."
+};
+
+/**
+ * Check if childPath is a subdirectory of parentPath.
+ *
+ * Called from `TsconfigPathsPlugin._selectPathsDataForContext` inside a loop
+ * over every tsconfig-paths context on every resolve, so it's worth keeping
+ * cheap. Compared to the previous `startsWith(normalize(parent + "/"))`
+ * version, this: checks the last char with `charCodeAt` instead of two
+ * `endsWith` calls; and skips `normalize()` entirely in the common case
+ * (parent has no trailing separator), since all we really need is the same
+ * anchoring effect — a cheap `startsWith` plus a separator char check on the
+ * byte immediately after `parentPath.length`.
+ * @param {string} parentPath parent directory path
+ * @param {string} childPath child path to check
+ * @returns {boolean} true if childPath is under parentPath
+ */
+const isSubPath = (parentPath, childPath) => {
+	const parentLen = parentPath.length;
+	if (parentLen === 0) {
+		// Match the old `normalize("" + "/") === "/"` fallback: an empty
+		// parent only "contains" a child that starts with a forward slash.
+		return childPath.length > 0 && childPath.charCodeAt(0) === CHAR_SLASH;
+	}
+	const lastChar = parentPath.charCodeAt(parentLen - 1);
+	if (lastChar === CHAR_SLASH || lastChar === CHAR_BACKSLASH) {
+		// Parent already ends with a separator — a plain prefix test is enough.
+		return childPath.startsWith(parentPath);
+	}
+	if (childPath.length <= parentLen) return false;
+	if (!childPath.startsWith(parentPath)) return false;
+	// Must be followed by a separator so "/app" doesn't match "/app-other".
+	const nextChar = childPath.charCodeAt(parentLen);
+	return nextChar === CHAR_SLASH || nextChar === CHAR_BACKSLASH;
+};
+
+module.exports.PathType = PathType;
+module.exports.createCachedBasename = createCachedBasename;
+module.exports.createCachedDirname = createCachedDirname;
+module.exports.createCachedJoin = createCachedJoin;
+module.exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx;
+module.exports.dirname = dirname;
+module.exports.getType = getType;
+module.exports.invalidSegmentRegEx = invalidSegmentRegEx;
+module.exports.isRelativeRequest = isRelativeRequest;
+module.exports.isSubPath = isSubPath;
+module.exports.join = join;
+module.exports.normalize = normalize;
Index: frontend/node_modules/enhanced-resolve/lib/util/process-browser.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/process-browser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/process-browser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+module.exports = {
+	/**
+	 * @type {Record<string, string>}
+	 */
+	versions: {},
+	// eslint-disable-next-line jsdoc/reject-function-type
+	/** @param {Function} fn function */
+	nextTick(fn) {
+		// eslint-disable-next-line prefer-rest-params
+		const args = Array.prototype.slice.call(arguments, 1);
+		Promise.resolve().then(() => {
+			// eslint-disable-next-line prefer-spread
+			fn.apply(null, args);
+		});
+	},
+};
Index: frontend/node_modules/enhanced-resolve/lib/util/strip-json-comments.js
===================================================================
--- frontend/node_modules/enhanced-resolve/lib/util/strip-json-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/lib/util/strip-json-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,177 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Natsu @xiaoxiaojx
+
+	This file contains code ported from strip-json-comments:
+	https://github.com/sindresorhus/strip-json-comments
+	Original license: MIT
+	Original author: Sindre Sorhus
+*/
+
+"use strict";
+
+/**
+ * @typedef {object} StripJsonCommentsOptions
+ * @property {boolean=} whitespace Replace comments with whitespace
+ * @property {boolean=} trailingCommas Strip trailing commas
+ */
+
+const singleComment = Symbol("singleComment");
+const multiComment = Symbol("multiComment");
+
+/**
+ * Strip without whitespace (returns empty string)
+ * @param {string} _string Unused
+ * @param {number} _start Unused
+ * @param {number} _end Unused
+ * @returns {string} Empty string for all input
+ */
+const stripWithoutWhitespace = (_string, _start, _end) => "";
+
+/**
+ * Replace all characters except ASCII spaces, tabs and line endings with regular spaces to ensure valid JSON output.
+ * @param {string} string String to process
+ * @param {number} start Start index
+ * @param {number} end End index
+ * @returns {string} Processed string with comments replaced by whitespace
+ */
+const stripWithWhitespace = (string, start, end) =>
+	string.slice(start, end).replace(/[^ \t\r\n]/g, " ");
+
+/**
+ * Check if a quote is escaped
+ * @param {string} jsonString JSON string
+ * @param {number} quotePosition Position of the quote
+ * @returns {boolean} True if the quote at the given position is escaped
+ */
+const isEscaped = (jsonString, quotePosition) => {
+	let index = quotePosition - 1;
+	let backslashCount = 0;
+
+	while (jsonString[index] === "\\") {
+		index -= 1;
+		backslashCount += 1;
+	}
+
+	return Boolean(backslashCount % 2);
+};
+
+/**
+ * Strip comments from JSON string
+ * @param {string} jsonString JSON string with potential comments
+ * @param {StripJsonCommentsOptions} options Options
+ * @returns {string} JSON string without comments
+ */
+function stripJsonComments(
+	jsonString,
+	{ whitespace = true, trailingCommas = false } = {},
+) {
+	if (typeof jsonString !== "string") {
+		throw new TypeError(
+			`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``,
+		);
+	}
+
+	const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
+
+	let isInsideString = false;
+	/** @type {false | typeof singleComment | typeof multiComment} */
+	let isInsideComment = false;
+	let offset = 0;
+	let buffer = "";
+	let result = "";
+	let commaIndex = -1;
+
+	for (let index = 0; index < jsonString.length; index++) {
+		const currentCharacter = jsonString[index];
+		const nextCharacter = jsonString[index + 1];
+
+		if (!isInsideComment && currentCharacter === '"') {
+			// Enter or exit string
+			const escaped = isEscaped(jsonString, index);
+			if (!escaped) {
+				isInsideString = !isInsideString;
+			}
+		}
+
+		if (isInsideString) {
+			continue;
+		}
+
+		if (!isInsideComment && currentCharacter + nextCharacter === "//") {
+			// Enter single-line comment
+			buffer += jsonString.slice(offset, index);
+			offset = index;
+			isInsideComment = singleComment;
+			index++;
+		} else if (
+			isInsideComment === singleComment &&
+			currentCharacter + nextCharacter === "\r\n"
+		) {
+			// Exit single-line comment via \r\n
+			index++;
+			isInsideComment = false;
+			buffer += strip(jsonString, offset, index);
+			offset = index;
+			continue;
+		} else if (isInsideComment === singleComment && currentCharacter === "\n") {
+			// Exit single-line comment via \n
+			isInsideComment = false;
+			buffer += strip(jsonString, offset, index);
+			offset = index;
+		} else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
+			// Enter multiline comment
+			buffer += jsonString.slice(offset, index);
+			offset = index;
+			isInsideComment = multiComment;
+			index++;
+			continue;
+		} else if (
+			isInsideComment === multiComment &&
+			currentCharacter + nextCharacter === "*/"
+		) {
+			// Exit multiline comment
+			index++;
+			isInsideComment = false;
+			buffer += strip(jsonString, offset, index + 1);
+			offset = index + 1;
+			continue;
+		} else if (trailingCommas && !isInsideComment) {
+			if (commaIndex !== -1) {
+				if (currentCharacter === "}" || currentCharacter === "]") {
+					// Strip trailing comma
+					buffer += jsonString.slice(offset, index);
+					result += strip(buffer, 0, 1) + buffer.slice(1);
+					buffer = "";
+					offset = index;
+					commaIndex = -1;
+				} else if (
+					currentCharacter !== " " &&
+					currentCharacter !== "\t" &&
+					currentCharacter !== "\r" &&
+					currentCharacter !== "\n"
+				) {
+					// Hit non-whitespace following a comma; comma is not trailing
+					buffer += jsonString.slice(offset, index);
+					offset = index;
+					commaIndex = -1;
+				}
+			} else if (currentCharacter === ",") {
+				// Flush buffer prior to this point, and save new comma index
+				result += buffer + jsonString.slice(offset, index);
+				buffer = "";
+				offset = index;
+				commaIndex = index;
+			}
+		}
+	}
+
+	const remaining =
+		isInsideComment === singleComment
+			? strip(jsonString, offset, jsonString.length)
+			: jsonString.slice(offset);
+
+	return result + buffer + remaining;
+}
+
+module.exports = stripJsonComments;
Index: frontend/node_modules/enhanced-resolve/package.json
===================================================================
--- frontend/node_modules/enhanced-resolve/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,83 @@
+{
+  "name": "enhanced-resolve",
+  "version": "5.21.6",
+  "description": "Offers a async require.resolve function. It's highly configurable.",
+  "homepage": "http://github.com/webpack/enhanced-resolve",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/webpack/enhanced-resolve.git"
+  },
+  "license": "MIT",
+  "author": "Tobias Koppers @sokra",
+  "main": "lib/index.js",
+  "browser": {
+    "process": "./lib/util/process-browser.js",
+    "module": "./lib/util/module-browser.js"
+  },
+  "types": "types.d.ts",
+  "files": [
+    "lib",
+    "types.d.ts",
+    "LICENSE"
+  ],
+  "scripts": {
+    "prepare": "husky",
+    "lint": "npm run lint:code && npm run lint:types && npm run lint:types-test && npm run lint:special && npm run fmt:check && npm run lint:spellcheck",
+    "lint:code": "eslint --cache .",
+    "lint:special": "node node_modules/tooling/inherit-types && node node_modules/tooling/generate-types",
+    "lint:types": "tsc",
+    "lint:types-test": "tsc -p tsconfig.types.test.json",
+    "lint:spellcheck": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
+    "fmt": "npm run fmt:base -- --log-level warn --write",
+    "fmt:check": "npm run fmt:base -- --check",
+    "fmt:base": "node_modules/prettier/bin/prettier.cjs --cache --ignore-unknown .",
+    "fix": "npm run fix:code && npm run fix:special",
+    "fix:code": "npm run lint:code -- --fix",
+    "fix:special": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/generate-types --write",
+    "type-report": "rimraf coverage && npm run cover:types && npm run cover:report && open-cli coverage/lcov-report/index.html",
+    "pretest": "npm run lint",
+    "test": "npm run test:coverage",
+    "test:only": "jest",
+    "test:watch": "npm run test:only -- --watch",
+    "test:coverage": "npm run test:only -- --collectCoverageFrom=\"lib/**/*.js\" --coverage",
+    "version": "changeset version",
+    "release": "changeset publish",
+    "benchmark": "node --max-old-space-size=4096 --hash-seed=1 --random-seed=1 --no-opt --predictable --predictable-gc-schedule --interpreted-frames-native-stack --allow-natives-syntax --expose-gc --no-concurrent-sweeping ./benchmark/run.mjs"
+  },
+  "lint-staged": {
+    "*.{js,cjs,mjs}": [
+      "eslint --cache --fix"
+    ],
+    "*": [
+      "prettier --cache --write --ignore-unknown",
+      "cspell --cache --no-must-find-files"
+    ]
+  },
+  "dependencies": {
+    "graceful-fs": "^4.2.4",
+    "tapable": "^2.3.3"
+  },
+  "devDependencies": {
+    "@changesets/cli": "^2.30.0",
+    "@changesets/get-github-info": "^0.8.0",
+    "@codspeed/core": "^5.2.0",
+    "@types/graceful-fs": "^4.1.6",
+    "@types/jest": "^30.0.0",
+    "@types/node": "^24.10.4",
+    "cspell": "^10.0.0",
+    "eslint": "^9.39.2",
+    "eslint-config-webpack": "^4.9.5",
+    "husky": "^9.1.7",
+    "jest": "^30.3.0",
+    "lint-staged": "^17.0.4",
+    "memfs": "^4.56.11",
+    "prettier": "^3.7.4",
+    "prettier-2": "npm:prettier@^2",
+    "tooling": "webpack/tooling#v1.26.1",
+    "tinybench": "^6.0.0",
+    "typescript": "^6.0.2"
+  },
+  "engines": {
+    "node": ">=10.13.0"
+  }
+}
Index: frontend/node_modules/enhanced-resolve/types.d.ts
===================================================================
--- frontend/node_modules/enhanced-resolve/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/enhanced-resolve/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2006 @@
+/*
+ * This file was automatically generated.
+ * DO NOT MODIFY BY HAND.
+ * Run `yarn fix:special` to update
+ */
+
+import { Buffer } from "buffer";
+import { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } from "tapable";
+import { URL as URL_Import } from "url";
+
+declare interface Abortable {
+	/**
+	 * When provided the corresponding `AbortController` can be used to cancel an asynchronous action.
+	 */
+	signal?: AbortSignal;
+}
+type Alias = string | false | string[];
+declare interface AliasOption {
+	alias: Alias;
+	name: string;
+	onlyModule?: boolean;
+}
+type AliasOptionNewRequest = string | false | string[];
+declare interface AliasOptions {
+	[index: string]: AliasOptionNewRequest;
+}
+type BaseFileSystem = FileSystem & SyncFileSystem;
+declare interface BaseResolveRequest {
+	/**
+	 * path
+	 */
+	path: string | false;
+
+	/**
+	 * content
+	 */
+	context?: Context;
+
+	/**
+	 * description file path
+	 */
+	descriptionFilePath?: string;
+
+	/**
+	 * description file root
+	 */
+	descriptionFileRoot?: string;
+
+	/**
+	 * description file data
+	 */
+	descriptionFileData?: JsonObject;
+
+	/**
+	 * tsconfig paths map
+	 */
+	tsconfigPathsMap?: null | TsconfigPathsMap;
+
+	/**
+	 * relative path
+	 */
+	relativePath?: string;
+
+	/**
+	 * true when need to ignore symlinks, otherwise false
+	 */
+	ignoreSymlinks?: boolean;
+
+	/**
+	 * true when full specified, otherwise false
+	 */
+	fullySpecified?: boolean;
+
+	/**
+	 * inner request for internal usage
+	 */
+	__innerRequest?: string;
+
+	/**
+	 * inner request for internal usage
+	 */
+	__innerRequest_request?: string;
+
+	/**
+	 * inner relative path for internal usage
+	 */
+	__innerRequest_relativePath?: string;
+}
+declare interface BasenameCacheEntry {
+	/**
+	 * cached dirname function
+	 */
+	fn: (maybePath: string, suffix?: string) => string;
+
+	/**
+	 * the underlying cache map
+	 */
+	cache: Map<string, Map<undefined | string, undefined | string>>;
+}
+type BufferEncoding =
+	| "ascii"
+	| "utf8"
+	| "utf-8"
+	| "utf16le"
+	| "utf-16le"
+	| "ucs2"
+	| "ucs-2"
+	| "base64"
+	| "base64url"
+	| "latin1"
+	| "binary"
+	| "hex";
+type BufferEncodingOption = "buffer" | { encoding: "buffer" };
+declare interface Cache {
+	[index: string]: undefined | ResolveRequest | ResolveRequest[];
+}
+declare class CachedInputFileSystem {
+	constructor(fileSystem: BaseFileSystem, duration: number);
+	fileSystem: BaseFileSystem;
+	lstat?: LStat;
+	lstatSync?: LStatSync;
+	stat: Stat;
+	statSync: StatSync;
+	readdir: Readdir;
+	readdirSync: ReaddirSync;
+	readFile: ReadFile;
+	readFileSync: ReadFileSync;
+	readJson?: (
+		pathOrFileDescription: PathOrFileDescriptor,
+		callback: (
+			err: null | Error | NodeJS.ErrnoException,
+			result?: JsonObject,
+		) => void,
+	) => void;
+	readJsonSync?: (pathOrFileDescription: PathOrFileDescriptor) => JsonObject;
+	readlink: Readlink;
+	readlinkSync: ReadlinkSync;
+	realpath?: RealPath;
+	realpathSync?: RealPathSync;
+	purge(
+		what?:
+			| string
+			| number
+			| Buffer
+			| URL_url
+			| (string | number | Buffer | URL_url)[]
+			| Set<string | number | Buffer | URL_url>,
+	): void;
+}
+declare class CloneBasenamePlugin {
+	constructor(
+		source:
+			| string
+			| AsyncSeriesBailHook<
+					[ResolveRequest, ResolveContext],
+					null | ResolveRequest
+			  >,
+		target:
+			| string
+			| AsyncSeriesBailHook<
+					[ResolveRequest, ResolveContext],
+					null | ResolveRequest
+			  >,
+	);
+	source:
+		| string
+		| AsyncSeriesBailHook<
+				[ResolveRequest, ResolveContext],
+				null | ResolveRequest
+		  >;
+	target:
+		| string
+		| AsyncSeriesBailHook<
+				[ResolveRequest, ResolveContext],
+				null | ResolveRequest
+		  >;
+	apply(resolver: Resolver): void;
+}
+declare interface CompiledAliasOption {
+	/**
+	 * original alias name
+	 */
+	name: string;
+
+	/**
+	 * name + "/" — precomputed to avoid per-resolve concat
+	 */
+	nameWithSlash: string;
+
+	/**
+	 * alias target(s)
+	 */
+	alias: Alias;
+
+	/**
+	 * normalized onlyModule flag
+	 */
+	onlyModule: boolean;
+
+	/**
+	 * absolute form of `name` (with slash ending), null when not absolute
+	 */
+	absolutePath: null | string;
+
+	/**
+	 * substring before the single "*" in `name`, null when no wildcard
+	 */
+	wildcardPrefix: null | string;
+
+	/**
+	 * substring after the single "*" in `name`, null when no wildcard
+	 */
+	wildcardSuffix: null | string;
+
+	/**
+	 * first character code of `name` — used as a cheap screen on the hot path. `-1` indicates "matches any first char" (empty wildcard prefix).
+	 */
+	firstCharCode: number;
+
+	/**
+	 * true when `alias` is an array — precomputed so the hot path skips `Array.isArray`
+	 */
+	arrayAlias: boolean;
+}
+declare interface CompiledAliasOptions {
+	/**
+	 * declaration-ordered list
+	 */
+	all: CompiledAliasOption[];
+
+	/**
+	 * bucketed by first char code
+	 */
+	byFirstChar: Map<number, CompiledAliasOption[]>;
+
+	/**
+	 * true when an empty-prefix wildcard is present
+	 */
+	hasAnyFirstChar: boolean;
+
+	/**
+	 * true when the bucket fast-path should be used at resolve time
+	 */
+	useBuckets: boolean;
+}
+type Context = KnownContext & Record<any, any>;
+declare interface Dirent<T extends string | Buffer = string> {
+	/**
+	 * true when is file, otherwise false
+	 */
+	isFile: () => boolean;
+
+	/**
+	 * true when is directory, otherwise false
+	 */
+	isDirectory: () => boolean;
+
+	/**
+	 * true when is block device, otherwise false
+	 */
+	isBlockDevice: () => boolean;
+
+	/**
+	 * true when is character device, otherwise false
+	 */
+	isCharacterDevice: () => boolean;
+
+	/**
+	 * true when is symbolic link, otherwise false
+	 */
+	isSymbolicLink: () => boolean;
+
+	/**
+	 * true when is FIFO, otherwise false
+	 */
+	isFIFO: () => boolean;
+
+	/**
+	 * true when is socket, otherwise false
+	 */
+	isSocket: () => boolean;
+
+	/**
+	 * name
+	 */
+	name: T;
+
+	/**
+	 * path
+	 */
+	parentPath: string;
+
+	/**
+	 * path
+	 */
+	path?: string;
+}
+declare interface DirnameCacheEntry {
+	/**
+	 * cached dirname function
+	 */
+	fn: (maybePath: string) => string;
+
+	/**
+	 * the underlying cache map
+	 */
+	cache: Map<string, string>;
+}
+type EncodingOption =
+	| undefined
+	| null
+	| "ascii"
+	| "utf8"
+	| "utf-8"
+	| "utf16le"
+	| "utf-16le"
+	| "ucs2"
+	| "ucs-2"
+	| "base64"
+	| "base64url"
+	| "latin1"
+	| "binary"
+	| "hex"
+	| ObjectEncodingOptions;
+type ErrorWithDetail = Error & { details?: string };
+declare interface ExtensionAliasOption {
+	alias: string | string[];
+	extension: string;
+}
+declare interface ExtensionAliasOptions {
+	[index: string]: string | string[];
+}
+declare interface FileSystem {
+	/**
+	 * read file method
+	 */
+	readFile: ReadFile;
+
+	/**
+	 * readdir method
+	 */
+	readdir: Readdir;
+
+	/**
+	 * read json method
+	 */
+	readJson?: (
+		pathOrFileDescription: PathOrFileDescriptor,
+		callback: (
+			err: null | Error | NodeJS.ErrnoException,
+			result?: JsonObject,
+		) => void,
+	) => void;
+
+	/**
+	 * read link method
+	 */
+	readlink: Readlink;
+
+	/**
+	 * lstat method
+	 */
+	lstat?: LStat;
+
+	/**
+	 * stat method
+	 */
+	stat: Stat;
+
+	/**
+	 * realpath method
+	 */
+	realpath?: RealPath;
+}
+type IBigIntStats = IStatsBase<bigint> & {
+	atimeNs: bigint;
+	mtimeNs: bigint;
+	ctimeNs: bigint;
+	birthtimeNs: bigint;
+};
+declare interface IStats {
+	/**
+	 * is file
+	 */
+	isFile: () => boolean;
+
+	/**
+	 * is directory
+	 */
+	isDirectory: () => boolean;
+
+	/**
+	 * is block device
+	 */
+	isBlockDevice: () => boolean;
+
+	/**
+	 * is character device
+	 */
+	isCharacterDevice: () => boolean;
+
+	/**
+	 * is symbolic link
+	 */
+	isSymbolicLink: () => boolean;
+
+	/**
+	 * is FIFO
+	 */
+	isFIFO: () => boolean;
+
+	/**
+	 * is socket
+	 */
+	isSocket: () => boolean;
+
+	/**
+	 * dev
+	 */
+	dev: number;
+
+	/**
+	 * ino
+	 */
+	ino: number;
+
+	/**
+	 * mode
+	 */
+	mode: number;
+
+	/**
+	 * nlink
+	 */
+	nlink: number;
+
+	/**
+	 * uid
+	 */
+	uid: number;
+
+	/**
+	 * gid
+	 */
+	gid: number;
+
+	/**
+	 * rdev
+	 */
+	rdev: number;
+
+	/**
+	 * size
+	 */
+	size: number;
+
+	/**
+	 * blksize
+	 */
+	blksize: number;
+
+	/**
+	 * blocks
+	 */
+	blocks: number;
+
+	/**
+	 * atime ms
+	 */
+	atimeMs: number;
+
+	/**
+	 * mtime ms
+	 */
+	mtimeMs: number;
+
+	/**
+	 * ctime ms
+	 */
+	ctimeMs: number;
+
+	/**
+	 * birthtime ms
+	 */
+	birthtimeMs: number;
+
+	/**
+	 * atime
+	 */
+	atime: Date;
+
+	/**
+	 * mtime
+	 */
+	mtime: Date;
+
+	/**
+	 * ctime
+	 */
+	ctime: Date;
+
+	/**
+	 * birthtime
+	 */
+	birthtime: Date;
+}
+declare interface IStatsBase<T> {
+	/**
+	 * is file
+	 */
+	isFile: () => boolean;
+
+	/**
+	 * is directory
+	 */
+	isDirectory: () => boolean;
+
+	/**
+	 * is block device
+	 */
+	isBlockDevice: () => boolean;
+
+	/**
+	 * is character device
+	 */
+	isCharacterDevice: () => boolean;
+
+	/**
+	 * is symbolic link
+	 */
+	isSymbolicLink: () => boolean;
+
+	/**
+	 * is FIFO
+	 */
+	isFIFO: () => boolean;
+
+	/**
+	 * is socket
+	 */
+	isSocket: () => boolean;
+
+	/**
+	 * dev
+	 */
+	dev: T;
+
+	/**
+	 * ino
+	 */
+	ino: T;
+
+	/**
+	 * mode
+	 */
+	mode: T;
+
+	/**
+	 * nlink
+	 */
+	nlink: T;
+
+	/**
+	 * uid
+	 */
+	uid: T;
+
+	/**
+	 * gid
+	 */
+	gid: T;
+
+	/**
+	 * rdev
+	 */
+	rdev: T;
+
+	/**
+	 * size
+	 */
+	size: T;
+
+	/**
+	 * blksize
+	 */
+	blksize: T;
+
+	/**
+	 * blocks
+	 */
+	blocks: T;
+
+	/**
+	 * atime ms
+	 */
+	atimeMs: T;
+
+	/**
+	 * mtime ms
+	 */
+	mtimeMs: T;
+
+	/**
+	 * ctime ms
+	 */
+	ctimeMs: T;
+
+	/**
+	 * birthtime ms
+	 */
+	birthtimeMs: T;
+
+	/**
+	 * atime
+	 */
+	atime: Date;
+
+	/**
+	 * mtime
+	 */
+	mtime: Date;
+
+	/**
+	 * ctime
+	 */
+	ctime: Date;
+
+	/**
+	 * birthtime
+	 */
+	birthtime: Date;
+}
+declare interface Iterator<T, Z> {
+	(
+		item: T,
+		callback: (err?: null | Error, result?: null | Z) => void,
+		i: number,
+	): void;
+}
+declare interface JoinCacheEntry {
+	/**
+	 * cached join function
+	 */
+	fn: (rootPath: string, request: string) => string;
+
+	/**
+	 * the underlying cache map
+	 */
+	cache: Map<string, Map<string, undefined | string>>;
+}
+declare interface JsonObject {
+	[index: string]:
+		| undefined
+		| null
+		| string
+		| number
+		| boolean
+		| JsonObject
+		| JsonValue[];
+}
+type JsonValue = null | string | number | boolean | JsonObject | JsonValue[];
+declare interface KnownContext {
+	/**
+	 * environments
+	 */
+	environments?: string[];
+}
+declare interface KnownHooks {
+	/**
+	 * resolve step hook
+	 */
+	resolveStep: SyncHook<
+		[
+			AsyncSeriesBailHook<
+				[ResolveRequest, ResolveContext],
+				null | ResolveRequest
+			>,
+			ResolveRequest,
+		]
+	>;
+
+	/**
+	 * no resolve hook
+	 */
+	noResolve: SyncHook<[ResolveRequest, Error]>;
+
+	/**
+	 * resolve hook
+	 */
+	resolve: AsyncSeriesBailHook<
+		[ResolveRequest, ResolveContext],
+		null | ResolveRequest
+	>;
+
+	/**
+	 * result hook
+	 */
+	result: AsyncSeriesHook<[ResolveRequest, ResolveContext]>;
+}
+declare interface LStat {
+	(
+		path: PathLike,
+		callback: (err: null | NodeJS.ErrnoException, result?: IStats) => void,
+	): void;
+	(
+		path: PathLike,
+		options: undefined | (StatOptions & { bigint?: false }),
+		callback: (err: null | NodeJS.ErrnoException, result?: IStats) => void,
+	): void;
+	(
+		path: PathLike,
+		options: StatOptions & { bigint: true },
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: IBigIntStats,
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		options: undefined | StatOptions,
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: IStats | IBigIntStats,
+		) => void,
+	): void;
+}
+declare interface LStatSync {
+	(path: PathLike, options?: undefined): IStats;
+	(
+		path: PathLike,
+		options?: StatSyncOptions & { bigint?: false; throwIfNoEntry: false },
+	): undefined | IStats;
+	(
+		path: PathLike,
+		options: StatSyncOptions & { bigint: true; throwIfNoEntry: false },
+	): undefined | IBigIntStats;
+	(path: PathLike, options?: StatSyncOptions & { bigint?: false }): IStats;
+	(path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
+	(
+		path: PathLike,
+		options: StatSyncOptions & { bigint: boolean; throwIfNoEntry?: false },
+	): IStats | IBigIntStats;
+	(
+		path: PathLike,
+		options?: StatSyncOptions,
+	): undefined | IStats | IBigIntStats;
+}
+declare class LogInfoPlugin {
+	constructor(
+		source:
+			| string
+			| AsyncSeriesBailHook<
+					[ResolveRequest, ResolveContext],
+					null | ResolveRequest
+			  >,
+	);
+	source:
+		| string
+		| AsyncSeriesBailHook<
+				[ResolveRequest, ResolveContext],
+				null | ResolveRequest
+		  >;
+	apply(resolver: Resolver): void;
+}
+declare interface ObjectEncodingOptions {
+	/**
+	 * encoding
+	 */
+	encoding?:
+		| null
+		| "ascii"
+		| "utf8"
+		| "utf-8"
+		| "utf16le"
+		| "utf-16le"
+		| "ucs2"
+		| "ucs-2"
+		| "base64"
+		| "base64url"
+		| "latin1"
+		| "binary"
+		| "hex";
+}
+declare interface ParsedIdentifier {
+	/**
+	 * request
+	 */
+	request: string;
+
+	/**
+	 * query
+	 */
+	query: string;
+
+	/**
+	 * fragment
+	 */
+	fragment: string;
+
+	/**
+	 * is directory
+	 */
+	directory: boolean;
+
+	/**
+	 * is module
+	 */
+	module: boolean;
+
+	/**
+	 * is file
+	 */
+	file: boolean;
+
+	/**
+	 * is internal
+	 */
+	internal: boolean;
+}
+declare interface PathCacheFunctions {
+	/**
+	 * cached join
+	 */
+	join: JoinCacheEntry;
+
+	/**
+	 * cached dirname
+	 */
+	dirname: DirnameCacheEntry;
+
+	/**
+	 * cached basename
+	 */
+	basename: BasenameCacheEntry;
+}
+type PathLike = string | Buffer | URL_url;
+type PathOrFileDescriptor = string | number | Buffer | URL_url;
+type Plugin =
+	| undefined
+	| null
+	| false
+	| ""
+	| 0
+	| { apply: (this: Resolver, resolver: Resolver) => void }
+	| ((this: Resolver, resolver: Resolver) => void);
+declare interface PnpApi {
+	/**
+	 * resolve to unqualified
+	 */
+	resolveToUnqualified: (
+		packageName: string,
+		issuer: string,
+		options: { considerBuiltins: boolean },
+	) => null | string;
+}
+declare interface ReadFile {
+	(
+		path: PathOrFileDescriptor,
+		options:
+			| undefined
+			| null
+			| ({ encoding?: null; flag?: string } & Abortable),
+		callback: (err: null | NodeJS.ErrnoException, result?: Buffer) => void,
+	): void;
+	(
+		path: PathOrFileDescriptor,
+		options:
+			| ({ encoding: BufferEncoding; flag?: string } & Abortable)
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex",
+		callback: (err: null | NodeJS.ErrnoException, result?: string) => void,
+	): void;
+	(
+		path: PathOrFileDescriptor,
+		options:
+			| undefined
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| (ObjectEncodingOptions & { flag?: string } & Abortable),
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: string | Buffer,
+		) => void,
+	): void;
+	(
+		path: PathOrFileDescriptor,
+		callback: (err: null | NodeJS.ErrnoException, result?: Buffer) => void,
+	): void;
+}
+declare interface ReadFileSync {
+	(
+		path: PathOrFileDescriptor,
+		options?: null | { encoding?: null; flag?: string },
+	): Buffer;
+	(
+		path: PathOrFileDescriptor,
+		options:
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| { encoding: BufferEncoding; flag?: string },
+	): string;
+	(
+		path: PathOrFileDescriptor,
+		options?:
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| (ObjectEncodingOptions & { flag?: string }),
+	): string | Buffer;
+}
+declare interface Readdir {
+	(
+		path: PathLike,
+		options:
+			| undefined
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| {
+					encoding:
+						| null
+						| "ascii"
+						| "utf8"
+						| "utf-8"
+						| "utf16le"
+						| "utf-16le"
+						| "ucs2"
+						| "ucs-2"
+						| "base64"
+						| "base64url"
+						| "latin1"
+						| "binary"
+						| "hex";
+					withFileTypes?: false;
+					recursive?: boolean;
+			  },
+		callback: (err: null | NodeJS.ErrnoException, files?: string[]) => void,
+	): void;
+	(
+		path: PathLike,
+		options:
+			| { encoding: "buffer"; withFileTypes?: false; recursive?: boolean }
+			| "buffer",
+		callback: (err: null | NodeJS.ErrnoException, files?: Buffer[]) => void,
+	): void;
+	(
+		path: PathLike,
+		options:
+			| undefined
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| (ObjectEncodingOptions & {
+					withFileTypes?: false;
+					recursive?: boolean;
+			  }),
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			files?: string[] | Buffer[],
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		callback: (err: null | NodeJS.ErrnoException, files?: string[]) => void,
+	): void;
+	(
+		path: PathLike,
+		options: ObjectEncodingOptions & {
+			withFileTypes: true;
+			recursive?: boolean;
+		},
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			files?: Dirent<string>[],
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		options: { encoding: "buffer"; withFileTypes: true; recursive?: boolean },
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			files: Dirent<Buffer>[],
+		) => void,
+	): void;
+}
+declare interface ReaddirSync {
+	(
+		path: PathLike,
+		options?:
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| {
+					encoding:
+						| null
+						| "ascii"
+						| "utf8"
+						| "utf-8"
+						| "utf16le"
+						| "utf-16le"
+						| "ucs2"
+						| "ucs-2"
+						| "base64"
+						| "base64url"
+						| "latin1"
+						| "binary"
+						| "hex";
+					withFileTypes?: false;
+					recursive?: boolean;
+			  },
+	): string[];
+	(
+		path: PathLike,
+		options:
+			| "buffer"
+			| { encoding: "buffer"; withFileTypes?: false; recursive?: boolean },
+	): Buffer[];
+	(
+		path: PathLike,
+		options?:
+			| null
+			| "ascii"
+			| "utf8"
+			| "utf-8"
+			| "utf16le"
+			| "utf-16le"
+			| "ucs2"
+			| "ucs-2"
+			| "base64"
+			| "base64url"
+			| "latin1"
+			| "binary"
+			| "hex"
+			| (ObjectEncodingOptions & {
+					withFileTypes?: false;
+					recursive?: boolean;
+			  }),
+	): string[] | Buffer[];
+	(
+		path: PathLike,
+		options: ObjectEncodingOptions & {
+			withFileTypes: true;
+			recursive?: boolean;
+		},
+	): Dirent<string>[];
+	(
+		path: PathLike,
+		options: { encoding: "buffer"; withFileTypes: true; recursive?: boolean },
+	): Dirent<Buffer>[];
+}
+declare interface Readlink {
+	(
+		path: PathLike,
+		options: EncodingOption,
+		callback: (err: null | NodeJS.ErrnoException, result?: string) => void,
+	): void;
+	(
+		path: PathLike,
+		options: BufferEncodingOption,
+		callback: (err: null | NodeJS.ErrnoException, result?: Buffer) => void,
+	): void;
+	(
+		path: PathLike,
+		options: EncodingOption,
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: string | Buffer,
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		callback: (err: null | NodeJS.ErrnoException, result?: string) => void,
+	): void;
+}
+declare interface ReadlinkSync {
+	(path: PathLike, options?: EncodingOption): string;
+	(path: PathLike, options: BufferEncodingOption): Buffer;
+	(path: PathLike, options?: EncodingOption): string | Buffer;
+}
+declare interface RealPath {
+	(
+		path: PathLike,
+		options: EncodingOption,
+		callback: (err: null | NodeJS.ErrnoException, result?: string) => void,
+	): void;
+	(
+		path: PathLike,
+		options: BufferEncodingOption,
+		callback: (err: null | NodeJS.ErrnoException, result?: Buffer) => void,
+	): void;
+	(
+		path: PathLike,
+		options: EncodingOption,
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: string | Buffer,
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		callback: (err: null | NodeJS.ErrnoException, result?: string) => void,
+	): void;
+}
+declare interface RealPathSync {
+	(path: PathLike, options?: EncodingOption): string;
+	(path: PathLike, options: BufferEncodingOption): Buffer;
+	(path: PathLike, options?: EncodingOption): string | Buffer;
+}
+declare interface ResolveContext {
+	/**
+	 * directories that was found on file system
+	 */
+	contextDependencies?: WriteOnlySet<string>;
+
+	/**
+	 * files that was found on file system
+	 */
+	fileDependencies?: WriteOnlySet<string>;
+
+	/**
+	 * dependencies that was not found on file system
+	 */
+	missingDependencies?: WriteOnlySet<string>;
+
+	/**
+	 * tip of the resolver call stack (a singly-linked list with Set-like API). For instance, `resolve → parsedResolve → describedResolve`. Accepts a legacy `Set<string>` for back-compat with older callers; it is normalized internally without a hot-path branch.
+	 */
+	stack?: StackEntry | Set<string>;
+
+	/**
+	 * log function
+	 */
+	log?: (str: string) => void;
+
+	/**
+	 * yield result, if provided plugins can return several results
+	 */
+	yield?: (request: ResolveRequest) => void;
+}
+declare interface ResolveFunction {
+	(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): string | false;
+	(
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): string | false;
+}
+declare interface ResolveFunctionAsync {
+	(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext: ResolveContext,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	(
+		context: Context,
+		path: string,
+		request: string,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	(
+		path: string,
+		request: string,
+		resolveContext: ResolveContext,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	(
+		path: string,
+		request: string,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+}
+declare interface ResolveFunctionPromise {
+	(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): Promise<string | false>;
+	(
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): Promise<string | false>;
+}
+type ResolveOptionsOptionalFS = Omit<
+	ResolveOptionsResolverFactoryObject_2,
+	"fileSystem"
+> &
+	Partial<Pick<ResolveOptionsResolverFactoryObject_2, "fileSystem">>;
+declare interface ResolveOptionsResolverFactoryObject_1 {
+	/**
+	 * alias
+	 */
+	alias: AliasOption[];
+
+	/**
+	 * fallback
+	 */
+	fallback: AliasOption[];
+
+	/**
+	 * alias fields
+	 */
+	aliasFields: Set<string | string[]>;
+
+	/**
+	 * extension alias
+	 */
+	extensionAlias: ExtensionAliasOption[];
+
+	/**
+	 * apply extension alias to exports field targets
+	 */
+	extensionAliasForExports: boolean;
+
+	/**
+	 * cache predicate
+	 */
+	cachePredicate: (predicate: ResolveRequest) => boolean;
+
+	/**
+	 * cache with context
+	 */
+	cacheWithContext: boolean;
+
+	/**
+	 * A list of exports field condition names.
+	 */
+	conditionNames: Set<string>;
+
+	/**
+	 * description files
+	 */
+	descriptionFiles: string[];
+
+	/**
+	 * enforce extension
+	 */
+	enforceExtension: boolean;
+
+	/**
+	 * exports fields
+	 */
+	exportsFields: Set<string | string[]>;
+
+	/**
+	 * imports fields
+	 */
+	importsFields: Set<string | string[]>;
+
+	/**
+	 * extensions
+	 */
+	extensions: Set<string>;
+
+	/**
+	 * fileSystem
+	 */
+	fileSystem: FileSystem;
+
+	/**
+	 * unsafe cache
+	 */
+	unsafeCache: false | Cache;
+
+	/**
+	 * symlinks
+	 */
+	symlinks: boolean;
+
+	/**
+	 * resolver
+	 */
+	resolver?: Resolver;
+
+	/**
+	 * modules
+	 */
+	modules: (string | string[])[];
+
+	/**
+	 * main fields
+	 */
+	mainFields: { name: string[]; forceRelative: boolean }[];
+
+	/**
+	 * main files
+	 */
+	mainFiles: Set<string>;
+
+	/**
+	 * plugins
+	 */
+	plugins: Plugin[];
+
+	/**
+	 * pnp API
+	 */
+	pnpApi: null | PnpApi;
+
+	/**
+	 * roots
+	 */
+	roots: Set<string>;
+
+	/**
+	 * fully specified
+	 */
+	fullySpecified: boolean;
+
+	/**
+	 * resolve to context
+	 */
+	resolveToContext: boolean;
+
+	/**
+	 * restrictions
+	 */
+	restrictions: Set<string | RegExp>;
+
+	/**
+	 * prefer relative
+	 */
+	preferRelative: boolean;
+
+	/**
+	 * prefer absolute
+	 */
+	preferAbsolute: boolean;
+
+	/**
+	 * tsconfig file path or config object
+	 */
+	tsconfig: string | boolean | TsconfigOptions;
+}
+declare interface ResolveOptionsResolverFactoryObject_2 {
+	/**
+	 * A list of module alias configurations or an object which maps key to value
+	 */
+	alias?: AliasOptions | AliasOption[];
+
+	/**
+	 * A list of module alias configurations or an object which maps key to value, applied only after modules option
+	 */
+	fallback?: AliasOptions | AliasOption[];
+
+	/**
+	 * An object which maps extension to extension aliases
+	 */
+	extensionAlias?: ExtensionAliasOptions;
+
+	/**
+	 * Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned); when enabled, matches TypeScript's behavior for packages that ship TS sources alongside compiled JS.
+	 */
+	extensionAliasForExports?: boolean;
+
+	/**
+	 * A list of alias fields in description files
+	 */
+	aliasFields?: (string | string[])[];
+
+	/**
+	 * A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
+	 */
+	cachePredicate?: (predicate: ResolveRequest) => boolean;
+
+	/**
+	 * Whether or not the unsafeCache should include request context as part of the cache key.
+	 */
+	cacheWithContext?: boolean;
+
+	/**
+	 * A list of description files to read from
+	 */
+	descriptionFiles?: string[];
+
+	/**
+	 * A list of exports field condition names.
+	 */
+	conditionNames?: string[];
+
+	/**
+	 * Enforce that a extension from extensions must be used
+	 */
+	enforceExtension?: boolean;
+
+	/**
+	 * A list of exports fields in description files
+	 */
+	exportsFields?: (string | string[])[];
+
+	/**
+	 * A list of imports fields in description files
+	 */
+	importsFields?: (string | string[])[];
+
+	/**
+	 * A list of extensions which should be tried for files
+	 */
+	extensions?: string[];
+
+	/**
+	 * The file system which should be used
+	 */
+	fileSystem: FileSystem;
+
+	/**
+	 * Use this cache object to unsafely cache the successful requests
+	 */
+	unsafeCache?: boolean | Cache;
+
+	/**
+	 * Resolve symlinks to their symlinked location
+	 */
+	symlinks?: boolean;
+
+	/**
+	 * A prepared Resolver to which the plugins are attached
+	 */
+	resolver?: Resolver;
+
+	/**
+	 * A list of directories to resolve modules from, can be absolute path or folder name
+	 */
+	modules?: string | string[];
+
+	/**
+	 * A list of main fields in description files
+	 */
+	mainFields?: (
+		| string
+		| string[]
+		| { name: string | string[]; forceRelative: boolean }
+	)[];
+
+	/**
+	 * A list of main files in directories
+	 */
+	mainFiles?: string[];
+
+	/**
+	 * A list of additional resolve plugins which should be applied
+	 */
+	plugins?: Plugin[];
+
+	/**
+	 * A PnP API that should be used - null is "never", undefined is "auto"
+	 */
+	pnpApi?: null | PnpApi;
+
+	/**
+	 * A list of root paths
+	 */
+	roots?: string[];
+
+	/**
+	 * The request is already fully specified and no extensions or directories are resolved for it
+	 */
+	fullySpecified?: boolean;
+
+	/**
+	 * Resolve to a context instead of a file
+	 */
+	resolveToContext?: boolean;
+
+	/**
+	 * A list of resolve restrictions
+	 */
+	restrictions?: (string | RegExp)[];
+
+	/**
+	 * Use only the sync constraints of the file system calls
+	 */
+	useSyncFileSystemCalls?: boolean;
+
+	/**
+	 * Prefer to resolve module requests as relative requests before falling back to modules
+	 */
+	preferRelative?: boolean;
+
+	/**
+	 * Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
+	 */
+	preferAbsolute?: boolean;
+
+	/**
+	 * TypeScript config file path or config object with configFile and references
+	 */
+	tsconfig?: string | boolean | TsconfigOptions;
+}
+type ResolveRequest = BaseResolveRequest & Partial<ParsedIdentifier>;
+declare abstract class Resolver {
+	fileSystem: FileSystem;
+	options: ResolveOptionsResolverFactoryObject_1;
+	pathCache: PathCacheFunctions;
+	hooks: KnownHooks;
+	ensureHook(
+		name:
+			| string
+			| AsyncSeriesBailHook<
+					[ResolveRequest, ResolveContext],
+					null | ResolveRequest
+			  >,
+	): AsyncSeriesBailHook<
+		[ResolveRequest, ResolveContext],
+		null | ResolveRequest
+	>;
+	getHook(
+		name:
+			| string
+			| AsyncSeriesBailHook<
+					[ResolveRequest, ResolveContext],
+					null | ResolveRequest
+			  >,
+	): AsyncSeriesBailHook<
+		[ResolveRequest, ResolveContext],
+		null | ResolveRequest
+	>;
+	resolveSync(
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): string | false;
+	resolveSync(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): string | false;
+	resolvePromise(
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): Promise<string | false>;
+	resolvePromise(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext?: ResolveContext,
+	): Promise<string | false>;
+	resolve(
+		path: string,
+		request: string,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	resolve(
+		path: string,
+		request: string,
+		resolveContext: ResolveContext,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	resolve(
+		context: Context,
+		path: string,
+		request: string,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	resolve(
+		context: Context,
+		path: string,
+		request: string,
+		resolveContext: ResolveContext,
+		callback: (
+			err: null | ErrorWithDetail,
+			res?: string | false,
+			req?: ResolveRequest,
+		) => void,
+	): void;
+	doResolve(
+		hook: AsyncSeriesBailHook<
+			[ResolveRequest, ResolveContext],
+			null | ResolveRequest
+		>,
+		request: ResolveRequest,
+		message: null | string,
+		resolveContext: ResolveContext,
+		callback: (err?: null | Error, result?: ResolveRequest) => void,
+	): void;
+	parse(identifier: string): ParsedIdentifier;
+	isModule(path: string): boolean;
+	isPrivate(path: string): boolean;
+	isDirectory(path: string): boolean;
+	normalize(path: string): string;
+	join(path: string, request: string): string;
+	dirname(path: string): string;
+	basename(path: string, suffix?: string): string;
+}
+declare abstract class StackEntry {
+	name?: string;
+	path: string | false;
+	request: string;
+	query: string;
+	fragment: string;
+	directory: boolean;
+	module: boolean;
+	parent?: StackEntry;
+
+	/**
+	 * Strings seeded by callers that still pass `stack: new Set([...])`.
+	 * Propagated through the chain so deeper `doResolve` calls still see
+	 * them during recursion checks. `undefined` in the common case so
+	 * there is no extra work on the hot path.
+	 */
+	preSeeded?: Set<string>;
+
+	/**
+	 * 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.
+	 */
+	has(query: StackEntry): boolean;
+
+	/**
+	 * Number of entries on the stack (oldest-to-newest length).
+	 */
+	get size(): number;
+
+	/**
+	 * 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.
+	 */
+	toString(): string;
+}
+declare interface Stat {
+	(
+		path: PathLike,
+		callback: (err: null | NodeJS.ErrnoException, result?: IStats) => void,
+	): void;
+	(
+		path: PathLike,
+		options: undefined | (StatOptions & { bigint?: false }),
+		callback: (err: null | NodeJS.ErrnoException, result?: IStats) => void,
+	): void;
+	(
+		path: PathLike,
+		options: StatOptions & { bigint: true },
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: IBigIntStats,
+		) => void,
+	): void;
+	(
+		path: PathLike,
+		options: undefined | StatOptions,
+		callback: (
+			err: null | NodeJS.ErrnoException,
+			result?: IStats | IBigIntStats,
+		) => void,
+	): void;
+}
+declare interface StatOptions {
+	/**
+	 * need bigint values
+	 */
+	bigint?: boolean;
+}
+declare interface StatSync {
+	(path: PathLike, options?: undefined): IStats;
+	(
+		path: PathLike,
+		options?: StatSyncOptions & { bigint?: false; throwIfNoEntry: false },
+	): undefined | IStats;
+	(
+		path: PathLike,
+		options: StatSyncOptions & { bigint: true; throwIfNoEntry: false },
+	): undefined | IBigIntStats;
+	(path: PathLike, options?: StatSyncOptions & { bigint?: false }): IStats;
+	(path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
+	(
+		path: PathLike,
+		options: StatSyncOptions & { bigint: boolean; throwIfNoEntry?: false },
+	): IStats | IBigIntStats;
+	(
+		path: PathLike,
+		options?: StatSyncOptions,
+	): undefined | IStats | IBigIntStats;
+}
+declare interface StatSyncOptions {
+	/**
+	 * need bigint values
+	 */
+	bigint?: boolean;
+
+	/**
+	 * throw if no entry
+	 */
+	throwIfNoEntry?: boolean;
+}
+declare interface SyncFileSystem {
+	/**
+	 * read file sync method
+	 */
+	readFileSync: ReadFileSync;
+
+	/**
+	 * read dir sync method
+	 */
+	readdirSync: ReaddirSync;
+
+	/**
+	 * read json sync method
+	 */
+	readJsonSync?: (pathOrFileDescription: PathOrFileDescriptor) => JsonObject;
+
+	/**
+	 * read link sync method
+	 */
+	readlinkSync: ReadlinkSync;
+
+	/**
+	 * lstat sync method
+	 */
+	lstatSync?: LStatSync;
+
+	/**
+	 * stat sync method
+	 */
+	statSync: StatSync;
+
+	/**
+	 * real path sync method
+	 */
+	realpathSync?: RealPathSync;
+}
+declare interface TsconfigOptions {
+	/**
+	 * A relative path to the tsconfig file based on cwd, or an absolute path of tsconfig file
+	 */
+	configFile?: string;
+
+	/**
+	 * References to other tsconfig files. 'auto' inherits from TypeScript config, or an array of relative/absolute paths
+	 */
+	references?: string[] | "auto";
+
+	/**
+	 * Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file
+	 */
+	baseUrl?: string;
+}
+declare interface TsconfigPathsData {
+	/**
+	 * tsconfig file data
+	 */
+	alias: CompiledAliasOptions;
+
+	/**
+	 * tsconfig file data
+	 */
+	modules: string[];
+}
+declare interface TsconfigPathsMap {
+	/**
+	 * main tsconfig paths data
+	 */
+	main: TsconfigPathsData;
+
+	/**
+	 * main tsconfig base URL (absolute path)
+	 */
+	mainContext: string;
+
+	/**
+	 * referenced tsconfig paths data mapped by baseUrl
+	 */
+	refs: { [index: string]: TsconfigPathsData };
+
+	/**
+	 * all contexts (main + refs) for quick lookup
+	 */
+	allContexts: { [index: string]: TsconfigPathsData };
+
+	/**
+	 * precomputed `Object.keys(allContexts)` — read-only; used on the `_selectPathsDataForContext` hot path
+	 */
+	contextList: string[];
+
+	/**
+	 * file dependencies
+	 */
+	fileDependencies: Set<string>;
+}
+declare class TsconfigPathsPlugin {
+	constructor(configFileOrOptions: string | true | TsconfigOptions);
+	isAutoConfigFile: boolean;
+	configFile: string;
+	references: "auto" | TsconfigReference[];
+	baseUrl?: string;
+	apply(resolver: Resolver): void;
+}
+declare interface TsconfigReference {
+	/**
+	 * Path to the referenced project
+	 */
+	path: string;
+}
+declare interface URL_url extends URL_Import {}
+declare interface WriteOnlySet<T> {
+	add: (item: T) => void;
+}
+declare function exports(
+	context: Context,
+	path: string,
+	request: string,
+	resolveContext: ResolveContext,
+	callback: (
+		err: null | ErrorWithDetail,
+		res?: string | false,
+		req?: ResolveRequest,
+	) => void,
+): void;
+declare function exports(
+	context: Context,
+	path: string,
+	request: string,
+	callback: (
+		err: null | ErrorWithDetail,
+		res?: string | false,
+		req?: ResolveRequest,
+	) => void,
+): void;
+declare function exports(
+	path: string,
+	request: string,
+	resolveContext: ResolveContext,
+	callback: (
+		err: null | ErrorWithDetail,
+		res?: string | false,
+		req?: ResolveRequest,
+	) => void,
+): void;
+declare function exports(
+	path: string,
+	request: string,
+	callback: (
+		err: null | ErrorWithDetail,
+		res?: string | false,
+		req?: ResolveRequest,
+	) => void,
+): void;
+declare namespace exports {
+	export const sync: ResolveFunction;
+	export const promise: ResolveFunctionPromise;
+	export function create(
+		options: ResolveOptionsOptionalFS,
+	): ResolveFunctionAsync;
+	export namespace create {
+		export const sync: (options: ResolveOptionsOptionalFS) => ResolveFunction;
+		export const promise: (
+			options: ResolveOptionsOptionalFS,
+		) => ResolveFunctionPromise;
+	}
+	export namespace ResolverFactory {
+		export let createResolver: (
+			options: ResolveOptionsResolverFactoryObject_2,
+		) => Resolver;
+	}
+	export const forEachBail: <T, Z>(
+		array: T[],
+		iterator: Iterator<T, Z>,
+		callback: (err?: null | Error, result?: null | Z, i?: number) => void,
+	) => void;
+	export type ResolveCallback = (
+		err: null | ErrorWithDetail,
+		res?: string | false,
+		req?: ResolveRequest,
+	) => void;
+	export {
+		CachedInputFileSystem,
+		CloneBasenamePlugin,
+		LogInfoPlugin,
+		TsconfigPathsPlugin,
+		ResolveOptionsOptionalFS,
+		BaseFileSystem,
+		PnpApi,
+		Resolver,
+		Context,
+		FileSystem,
+		ResolveContext,
+		ResolveRequest,
+		SyncFileSystem,
+		Plugin,
+		ResolveOptionsResolverFactoryObject_2 as ResolveOptions,
+		ResolveFunctionAsync,
+		ResolveFunction,
+		ResolveFunctionPromise,
+	};
+}
+
+export = exports;
