Index: frontend/node_modules/watchpack/LICENSE
===================================================================
--- frontend/node_modules/watchpack/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/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/watchpack/README.md
===================================================================
--- frontend/node_modules/watchpack/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,133 @@
+# watchpack
+
+Wrapper library for directory and file watching.
+
+[![Test](https://github.com/webpack/watchpack/actions/workflows/test.yml/badge.svg)](https://github.com/webpack/watchpack/actions/workflows/test.yml)
+[![Codecov](https://codecov.io/gh/webpack/watchpack/graph/badge.svg?token=8xk2OrrxWm)](https://codecov.io/gh/webpack/watchpack)
+[![Downloads](https://img.shields.io/npm/dm/watchpack.svg)](https://www.npmjs.com/package/watchpack)
+
+## Concept
+
+watchpack high level API doesn't map directly to watchers. Instead a three level architecture ensures that for each directory only a single watcher exists.
+
+- The high level API requests `DirectoryWatchers` from a `WatcherManager`, which ensures that only a single `DirectoryWatcher` per directory is created.
+- A user-faced `Watcher` can be obtained from a `DirectoryWatcher` and provides a filtered view on the `DirectoryWatcher`.
+- Reference-counting is used on the `DirectoryWatcher` and `Watcher` to decide when to close them.
+- The real watchers are created by the `DirectoryWatcher`.
+- Files are never watched directly. This should keep the watcher count low.
+- Watching can be started in the past. This way watching can start after file reading.
+- Symlinks are not followed, instead the symlink is watched.
+
+## API
+
+```javascript
+const Watchpack = require("watchpack");
+
+const wp = new Watchpack({
+	// options:
+	aggregateTimeout: 1000,
+	// fire "aggregated" event when after a change for 1000ms no additional change occurred
+	// aggregated defaults to undefined, which doesn't fire an "aggregated" event
+
+	poll: true,
+	// poll: true - use polling with the default interval
+	// poll: 10000 - use polling with an interval of 10s
+	// poll defaults to undefined, which prefer native watching methods
+	// Note: enable polling when watching on a network path
+	// When WATCHPACK_POLLING environment variable is set it will override this option
+
+	followSymlinks: true,
+	// true: follows symlinks and watches symlinks and real files
+	//   (This makes sense when symlinks has not been resolved yet, comes with a performance hit)
+	// false (default): watches only specified item they may be real files or symlinks
+	//   (This makes sense when symlinks has already been resolved)
+
+	ignored: "**/.git",
+	// ignored: "string" - a glob pattern for files or folders that should not be watched
+	// ignored: ["string", "string"] - multiple glob patterns that should be ignored
+	// ignored: /regexp/ - a regular expression for files or folders that should not be watched
+	// ignored: (entry) => boolean - an arbitrary function which must return truthy to ignore an entry
+	// For all cases expect the arbitrary function the path will have path separator normalized to '/'.
+	// All subdirectories are ignored too
+});
+
+// Watchpack.prototype.watch({
+//   files: Iterable<string>,
+//   directories: Iterable<string>,
+//   missing: Iterable<string>,
+//   startTime?: number
+// })
+wp.watch({
+	files: listOfFiles,
+	directories: listOfDirectories,
+	missing: listOfNotExistingItems,
+	startTime: Date.now() - 10000,
+});
+// starts watching these files and directories
+// calling this again will override the files and directories
+// files: can be files or directories, for files: content and existence changes are tracked
+//        for directories: only existence and timestamp changes are tracked
+// directories: only directories, directory content (and content of children, ...) and
+//              existence changes are tracked.
+//              assumed to exist, when directory is not found without further information a remove event is emitted
+// missing: can be files or directories,
+//          only existence changes are tracked
+//          expected to not exist, no remove event is emitted when not found initially
+// files and directories are assumed to exist, when they are not found without further information a remove event is emitted
+// missing is assumed to not exist and no remove event is emitted
+
+wp.on("change", (filePath, mtime, explanation) => {
+	// filePath: the changed file
+	// mtime: last modified time for the changed file
+	// explanation: textual information how this change was detected
+});
+
+wp.on("remove", (filePath, explanation) => {
+	// filePath: the removed file or directory
+	// explanation: textual information how this change was detected
+});
+
+wp.on("aggregated", (changes, removals) => {
+	// changes: a Set of all changed files
+	// removals: a Set of all removed files
+	// watchpack gives up ownership on these Sets.
+});
+
+// Watchpack.prototype.pause()
+wp.pause();
+// stops emitting events, but keeps watchers open
+// next "watch" call can reuse the watchers
+// The watcher will keep aggregating events
+// which can be received with getAggregated()
+
+// Watchpack.prototype.close()
+wp.close();
+// stops emitting events and closes all watchers
+
+// Watchpack.prototype.getAggregated(): { changes: Set<string>, removals: Set<string> }
+const { changes, removals } = wp.getAggregated();
+// returns the current aggregated info and removes that from the watcher
+// The next aggregated event won't include that info and will only emitted
+// when futher changes happen
+// Can also be used when paused.
+
+// Watchpack.prototype.collectTimeInfoEntries(fileInfoEntries: Map<string, Entry>, directoryInfoEntries: Map<string, Entry>)
+wp.collectTimeInfoEntries(fileInfoEntries, directoryInfoEntries);
+// collects time info objects for all known files and directories
+// this include info from files not directly watched
+// key: absolute path, value: object with { safeTime, timestamp }
+// safeTime: a point in time at which it is safe to say all changes happened before that
+// timestamp: only for files, the mtime timestamp of the file
+
+// Watchpack.prototype.getTimeInfoEntries()
+const fileTimes = wp.getTimeInfoEntries();
+// returns a Map with all known time info objects for files and directories
+// similar to collectTimeInfoEntries but returns a single map with all entries
+
+// (deprecated)
+// Watchpack.prototype.getTimes()
+const fileTimesOld = wp.getTimes();
+// returns an object with all known change times for files
+// this include timestamps from files not directly watched
+// key: absolute path, value: timestamp as number
+```
Index: frontend/node_modules/watchpack/lib/DirectoryWatcher.js
===================================================================
--- frontend/node_modules/watchpack/lib/DirectoryWatcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/DirectoryWatcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,965 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const { EventEmitter } = require("events");
+const path = require("path");
+const fs = require("graceful-fs");
+
+const watchEventSource = require("./watchEventSource");
+
+/** @typedef {import("./index").IgnoredFunction} IgnoredFunction */
+/** @typedef {import("./index").EventType} EventType */
+/** @typedef {import("./index").TimeInfoEntries} TimeInfoEntries */
+/** @typedef {import("./index").Entry} Entry */
+/** @typedef {import("./index").ExistenceOnlyTimeEntry} ExistenceOnlyTimeEntry */
+/** @typedef {import("./index").OnlySafeTimeEntry} OnlySafeTimeEntry */
+/** @typedef {import("./index").EventMap} EventMap */
+/** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
+/** @typedef {import("./watchEventSource").Watcher} EventSourceWatcher */
+
+/** @type {ExistenceOnlyTimeEntry} */
+const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({});
+
+let FS_ACCURACY = 2000;
+
+const IS_OSX = require("os").platform() === "darwin";
+const IS_WIN = require("os").platform() === "win32";
+
+const { WATCHPACK_POLLING } = process.env;
+const FORCE_POLLING =
+	// @ts-expect-error avoid additional checks
+	`${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
+		? +WATCHPACK_POLLING
+		: Boolean(WATCHPACK_POLLING) && WATCHPACK_POLLING !== "false";
+
+/**
+ * @param {string} str string
+ * @returns {string} lower cased string
+ */
+function withoutCase(str) {
+	return str.toLowerCase();
+}
+
+/**
+ * @param {number} times times
+ * @param {() => void} callback callback
+ * @returns {() => void} result
+ */
+function needCalls(times, callback) {
+	return function needCallsCallback() {
+		if (--times === 0) {
+			return callback();
+		}
+	};
+}
+
+/**
+ * @param {Entry} entry entry
+ */
+function fixupEntryAccuracy(entry) {
+	if (entry.accuracy > FS_ACCURACY) {
+		entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
+		entry.accuracy = FS_ACCURACY;
+	}
+}
+
+/**
+ * @param {number=} mtime mtime
+ */
+function ensureFsAccuracy(mtime) {
+	if (!mtime) return;
+	if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
+	else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
+	else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
+	else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
+}
+
+/**
+ * @typedef {object} FileWatcherEvents
+ * @property {(type: EventType) => void} initial-missing initial missing event
+ * @property {(mtime: number, type: EventType, initial: boolean) => void} change change event
+ * @property {(type: EventType) => void} remove remove event
+ * @property {() => void} closed closed event
+ */
+
+/**
+ * @typedef {object} DirectoryWatcherEvents
+ * @property {(type: EventType) => void} initial-missing initial missing event
+ * @property {((file: string, mtime: number, type: EventType, initial: boolean) => void)} change change event
+ * @property {(type: EventType) => void} remove remove event
+ * @property {() => void} closed closed event
+ */
+
+/**
+ * @template {EventMap} T
+ * @extends {EventEmitter<{ [K in keyof T]: Parameters<T[K]> }>}
+ */
+class Watcher extends EventEmitter {
+	/**
+	 * @param {DirectoryWatcher} directoryWatcher a directory watcher
+	 * @param {string} target a target to watch
+	 * @param {number=} startTime start time
+	 */
+	constructor(directoryWatcher, target, startTime) {
+		super();
+		this.directoryWatcher = directoryWatcher;
+		this.path = target;
+		this.startTime = startTime && +startTime;
+	}
+
+	/**
+	 * @param {number} mtime mtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @returns {boolean} true of start time less than mtile, otherwise false
+	 */
+	checkStartTime(mtime, initial) {
+		const { startTime } = this;
+		if (typeof startTime !== "number") return !initial;
+		return startTime <= mtime;
+	}
+
+	close() {
+		// @ts-expect-error bad typing in EventEmitter
+		this.emit("closed");
+	}
+}
+
+/** @typedef {Set<string>} InitialScanRemoved */
+
+/**
+ * @typedef {object} WatchpackEvents
+ * @property {(target: string, mtime: string, type: EventType, initial: boolean) => void} change change event
+ * @property {() => void} closed closed event
+ */
+
+/**
+ * @typedef {object} DirectoryWatcherOptions
+ * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false
+ * @property {IgnoredFunction=} ignored ignore some files from watching (glob pattern or regexp)
+ * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false
+ */
+
+/**
+ * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>}
+ */
+class DirectoryWatcher extends EventEmitter {
+	/**
+	 * @param {WatcherManager} watcherManager a watcher manager
+	 * @param {string} directoryPath directory path
+	 * @param {DirectoryWatcherOptions=} options options
+	 */
+	constructor(watcherManager, directoryPath, options = {}) {
+		super();
+		if (FORCE_POLLING) {
+			options.poll = FORCE_POLLING;
+		}
+		this.watcherManager = watcherManager;
+		this.options = options;
+		this.path = directoryPath;
+		// safeTime is the point in time after which reading is safe to be unchanged
+		// timestamp is a value that should be compared with another timestamp (mtime)
+		/** @type {Map<string, Entry>} */
+		this.files = new Map();
+		/** @type {Map<string, number>} */
+		this.filesWithoutCase = new Map();
+		/** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */
+		this.directories = new Map();
+		this.lastWatchEvent = 0;
+		this.initialScan = true;
+		this.ignored = options.ignored || (() => false);
+		this.nestedWatching = false;
+		/** @type {number | false} */
+		this.polledWatching =
+			typeof options.poll === "number"
+				? options.poll
+				: options.poll
+					? 5007
+					: false;
+		/** @type {undefined | NodeJS.Timeout} */
+		this.timeout = undefined;
+		/** @type {null | InitialScanRemoved} */
+		this.initialScanRemoved = new Set();
+		/** @type {undefined | number} */
+		this.initialScanFinished = undefined;
+		/** @type {Map<string, Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>>} */
+		this.watchers = new Map();
+		/** @type {Watcher<FileWatcherEvents> | null} */
+		this.parentWatcher = null;
+		this.refs = 0;
+		/** @type {Map<string, boolean>} */
+		this._activeEvents = new Map();
+		this.closed = false;
+		this.scanning = false;
+		this.scanAgain = false;
+		this.scanAgainInitial = false;
+
+		this.createWatcher();
+		this.doScan(true);
+	}
+
+	createWatcher() {
+		try {
+			if (this.polledWatching) {
+				/** @type {EventSourceWatcher} */
+				(this.watcher) = /** @type {EventSourceWatcher} */ ({
+					close: () => {
+						if (this.timeout) {
+							clearTimeout(this.timeout);
+							this.timeout = undefined;
+						}
+					},
+				});
+			} else {
+				if (IS_OSX) {
+					this.watchInParentDirectory();
+				}
+				this.watcher =
+					/** @type {EventSourceWatcher} */
+					(watchEventSource.watch(this.path));
+				this.watcher.on("change", this.onWatchEvent.bind(this));
+				this.watcher.on("error", this.onWatcherError.bind(this));
+			}
+		} catch (err) {
+			this.onWatcherError(err);
+		}
+	}
+
+	/**
+	 * @template {(watcher: Watcher<EventMap>) => void} T
+	 * @param {string} path path
+	 * @param {T} fn function
+	 */
+	forEachWatcher(path, fn) {
+		const watchers = this.watchers.get(withoutCase(path));
+		if (watchers !== undefined) {
+			for (const w of watchers) {
+				fn(w);
+			}
+		}
+	}
+
+	/**
+	 * @param {string} itemPath an item path
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {EventType} type even type
+	 */
+	setMissing(itemPath, initial, type) {
+		if (this.initialScan) {
+			/** @type {InitialScanRemoved} */
+			(this.initialScanRemoved).add(itemPath);
+		}
+
+		const oldDirectory = this.directories.get(itemPath);
+		if (oldDirectory) {
+			if (this.nestedWatching) {
+				/** @type {Watcher<DirectoryWatcherEvents>} */
+				(oldDirectory).close();
+			}
+			this.directories.delete(itemPath);
+			this.forEachWatcher(itemPath, (w) => w.emit("remove", type));
+			if (!initial) {
+				this.forEachWatcher(this.path, (w) =>
+					w.emit("change", itemPath, null, type, initial),
+				);
+			}
+		}
+
+		const oldFile = this.files.get(itemPath);
+		if (oldFile) {
+			this.files.delete(itemPath);
+			const key = withoutCase(itemPath);
+			const count = /** @type {number} */ (this.filesWithoutCase.get(key)) - 1;
+			if (count <= 0) {
+				this.filesWithoutCase.delete(key);
+				this.forEachWatcher(itemPath, (w) => w.emit("remove", type));
+			} else {
+				this.filesWithoutCase.set(key, count);
+			}
+
+			if (!initial) {
+				this.forEachWatcher(this.path, (w) =>
+					w.emit("change", itemPath, null, type, initial),
+				);
+			}
+		}
+	}
+
+	/**
+	 * @param {string} target a target to set file time
+	 * @param {number} mtime mtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {boolean} ignoreWhenEqual true to ignore when equal, otherwise false
+	 * @param {EventType} type type
+	 */
+	setFileTime(target, mtime, initial, ignoreWhenEqual, type) {
+		const now = Date.now();
+
+		if (this.ignored(target)) return;
+
+		const old = this.files.get(target);
+
+		let safeTime;
+		let accuracy;
+		if (initial) {
+			safeTime = Math.min(now, mtime) + FS_ACCURACY;
+			accuracy = FS_ACCURACY;
+		} else {
+			safeTime = now;
+			accuracy = 0;
+
+			if (old && old.timestamp === mtime && mtime + FS_ACCURACY < now) {
+				// We are sure that mtime is untouched
+				// This can be caused by some file attribute change
+				// e. g. when access time has been changed
+				// but the file content is untouched
+				return;
+			}
+		}
+
+		if (ignoreWhenEqual && old && old.timestamp === mtime) return;
+
+		this.files.set(target, {
+			safeTime,
+			accuracy,
+			timestamp: mtime,
+		});
+
+		if (!old) {
+			const key = withoutCase(target);
+			const count = this.filesWithoutCase.get(key);
+			this.filesWithoutCase.set(key, (count || 0) + 1);
+			if (count !== undefined) {
+				// There is already a file with case-insensitive-equal name
+				// On a case-insensitive filesystem we may miss the renaming
+				// when only casing is changed.
+				// To be sure that our information is correct
+				// we trigger a rescan here
+				this.doScan(false);
+			}
+
+			this.forEachWatcher(target, (w) => {
+				if (!initial || w.checkStartTime(safeTime, initial)) {
+					w.emit("change", mtime, type);
+				}
+			});
+		} else if (!initial) {
+			this.forEachWatcher(target, (w) => w.emit("change", mtime, type));
+		}
+		this.forEachWatcher(this.path, (w) => {
+			if (!initial || w.checkStartTime(safeTime, initial)) {
+				w.emit("change", target, safeTime, type, initial);
+			}
+		});
+	}
+
+	/**
+	 * @param {string} directoryPath directory path
+	 * @param {number} birthtime birthtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {EventType} type even type
+	 */
+	setDirectory(directoryPath, birthtime, initial, type) {
+		if (this.ignored(directoryPath)) return;
+		if (directoryPath === this.path) {
+			if (!initial) {
+				this.forEachWatcher(this.path, (w) =>
+					w.emit("change", directoryPath, birthtime, type, initial),
+				);
+			}
+		} else {
+			const old = this.directories.get(directoryPath);
+			if (!old) {
+				const now = Date.now();
+
+				if (this.nestedWatching) {
+					this.createNestedWatcher(directoryPath);
+				} else {
+					this.directories.set(directoryPath, true);
+				}
+
+				const safeTime = initial ? Math.min(now, birthtime) + FS_ACCURACY : now;
+
+				this.forEachWatcher(directoryPath, (w) => {
+					if (!initial || w.checkStartTime(safeTime, false)) {
+						w.emit("change", birthtime, type);
+					}
+				});
+				this.forEachWatcher(this.path, (w) => {
+					if (!initial || w.checkStartTime(safeTime, initial)) {
+						w.emit("change", directoryPath, safeTime, type, initial);
+					}
+				});
+			}
+		}
+	}
+
+	/**
+	 * @param {string} directoryPath directory path
+	 */
+	createNestedWatcher(directoryPath) {
+		const watcher = this.watcherManager.watchDirectory(directoryPath, 1);
+		watcher.on("change", (target, mtime, type, initial) => {
+			this.forEachWatcher(this.path, (w) => {
+				if (!initial || w.checkStartTime(mtime, initial)) {
+					w.emit("change", target, mtime, type, initial);
+				}
+			});
+		});
+		this.directories.set(directoryPath, watcher);
+	}
+
+	/**
+	 * @param {boolean} flag true when nested, otherwise false
+	 */
+	setNestedWatching(flag) {
+		if (this.nestedWatching !== Boolean(flag)) {
+			this.nestedWatching = Boolean(flag);
+			if (this.nestedWatching) {
+				for (const directory of this.directories.keys()) {
+					this.createNestedWatcher(directory);
+				}
+			} else {
+				for (const [directory, watcher] of this.directories) {
+					/** @type {Watcher<DirectoryWatcherEvents>} */
+					(watcher).close();
+					this.directories.set(directory, true);
+				}
+			}
+		}
+	}
+
+	/**
+	 * @param {string} target a target to watch
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} watcher
+	 */
+	watch(target, startTime) {
+		const key = withoutCase(target);
+		let watchers = this.watchers.get(key);
+		if (watchers === undefined) {
+			watchers = new Set();
+			this.watchers.set(key, watchers);
+		}
+		this.refs++;
+		const watcher =
+			/** @type {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} */
+			(new Watcher(this, target, startTime));
+		watcher.on("closed", () => {
+			if (--this.refs <= 0) {
+				this.close();
+				return;
+			}
+			watchers.delete(watcher);
+			if (watchers.size === 0) {
+				this.watchers.delete(key);
+				if (this.path === target) this.setNestedWatching(false);
+			}
+		});
+		watchers.add(watcher);
+		let safeTime;
+		if (target === this.path) {
+			this.setNestedWatching(true);
+			safeTime = this.lastWatchEvent;
+			for (const entry of this.files.values()) {
+				fixupEntryAccuracy(entry);
+				safeTime = Math.max(safeTime, entry.safeTime);
+			}
+		} else {
+			const entry = this.files.get(target);
+			if (entry) {
+				fixupEntryAccuracy(entry);
+				safeTime = entry.safeTime;
+			} else {
+				safeTime = 0;
+			}
+		}
+		if (safeTime) {
+			if (startTime && safeTime >= startTime) {
+				process.nextTick(() => {
+					if (this.closed) return;
+					if (target === this.path) {
+						/** @type {Watcher<DirectoryWatcherEvents>} */
+						(watcher).emit(
+							"change",
+							target,
+							safeTime,
+							"watch (outdated on attach)",
+							true,
+						);
+					} else {
+						/** @type {Watcher<FileWatcherEvents>} */
+						(watcher).emit(
+							"change",
+							safeTime,
+							"watch (outdated on attach)",
+							true,
+						);
+					}
+				});
+			}
+		} else if (this.initialScan) {
+			if (
+				/** @type {InitialScanRemoved} */
+				(this.initialScanRemoved).has(target)
+			) {
+				process.nextTick(() => {
+					if (this.closed) return;
+					watcher.emit("remove");
+				});
+			}
+		} else if (
+			target !== this.path &&
+			!this.directories.has(target) &&
+			watcher.checkStartTime(
+				/** @type {number} */
+				(this.initialScanFinished),
+				false,
+			)
+		) {
+			process.nextTick(() => {
+				if (this.closed) return;
+				watcher.emit("initial-missing", "watch (missing on attach)");
+			});
+		}
+		return watcher;
+	}
+
+	/**
+	 * @param {EventType} eventType event type
+	 * @param {string=} filename filename
+	 */
+	onWatchEvent(eventType, filename) {
+		if (this.closed) return;
+		if (!filename) {
+			// In some cases no filename is provided
+			// This seem to happen on windows
+			// So some event happened but we don't know which file is affected
+			// We have to do a full scan of the directory
+			this.doScan(false);
+			return;
+		}
+
+		const target = path.join(this.path, filename);
+		if (this.ignored(target)) return;
+
+		if (this._activeEvents.get(filename) === undefined) {
+			this._activeEvents.set(filename, false);
+			const checkStats = () => {
+				if (this.closed) return;
+				this._activeEvents.set(filename, false);
+				fs.lstat(target, (err, stats) => {
+					if (this.closed) return;
+					if (this._activeEvents.get(filename) === true) {
+						process.nextTick(checkStats);
+						return;
+					}
+					this._activeEvents.delete(filename);
+					// ENOENT happens when the file/directory doesn't exist
+					// EPERM happens when the containing directory doesn't exist
+					if (err) {
+						if (
+							err.code !== "ENOENT" &&
+							err.code !== "EPERM" &&
+							err.code !== "EBUSY"
+						) {
+							this.onStatsError(err);
+						} else if (
+							filename === path.basename(this.path) && // This may indicate that the directory itself was removed
+							!fs.existsSync(this.path)
+						) {
+							this.onDirectoryRemoved("stat failed");
+						}
+					}
+					this.lastWatchEvent = Date.now();
+					if (!stats) {
+						this.setMissing(target, false, eventType);
+					} else if (stats.isDirectory()) {
+						this.setDirectory(target, +stats.birthtime || 1, false, eventType);
+					} else if (stats.isFile() || stats.isSymbolicLink()) {
+						if (stats.mtime) {
+							ensureFsAccuracy(+stats.mtime);
+						}
+						this.setFileTime(
+							target,
+							+stats.mtime || +stats.ctime || 1,
+							false,
+							false,
+							eventType,
+						);
+					}
+				});
+			};
+			process.nextTick(checkStats);
+		} else {
+			this._activeEvents.set(filename, true);
+		}
+	}
+
+	/**
+	 * @param {unknown=} err error
+	 */
+	onWatcherError(err) {
+		if (this.closed) return;
+		if (err) {
+			if (
+				/** @type {NodeJS.ErrnoException} */
+				(err).code !== "EPERM" &&
+				/** @type {NodeJS.ErrnoException} */
+				(err).code !== "ENOENT"
+			) {
+				// eslint-disable-next-line no-console
+				console.error(`Watchpack Error (watcher): ${err}`);
+			}
+			this.onDirectoryRemoved("watch error");
+		}
+	}
+
+	/**
+	 * @param {Error | NodeJS.ErrnoException=} err error
+	 */
+	onStatsError(err) {
+		if (err) {
+			// eslint-disable-next-line no-console
+			console.error(`Watchpack Error (stats): ${err}`);
+		}
+	}
+
+	/**
+	 * @param {Error | NodeJS.ErrnoException=} err error
+	 */
+	onScanError(err) {
+		if (err) {
+			// eslint-disable-next-line no-console
+			console.error(`Watchpack Error (initial scan): ${err}`);
+		}
+		this.onScanFinished();
+	}
+
+	onScanFinished() {
+		if (this.polledWatching) {
+			this.timeout = setTimeout(() => {
+				if (this.closed) return;
+				this.doScan(false);
+			}, this.polledWatching);
+		}
+	}
+
+	/**
+	 * @param {string} reason a reason
+	 */
+	onDirectoryRemoved(reason) {
+		if (this.watcher) {
+			this.watcher.close();
+			this.watcher = null;
+		}
+		this.watchInParentDirectory();
+		const type = /** @type {EventType} */ (`directory-removed (${reason})`);
+		for (const directory of this.directories.keys()) {
+			this.setMissing(directory, false, type);
+		}
+		for (const file of this.files.keys()) {
+			this.setMissing(file, false, type);
+		}
+	}
+
+	watchInParentDirectory() {
+		if (!this.parentWatcher) {
+			const parentDir = path.dirname(this.path);
+			// avoid watching in the root directory
+			// removing directories in the root directory is not supported
+			if (path.dirname(parentDir) === parentDir) return;
+
+			this.parentWatcher = this.watcherManager.watchFile(this.path, 1);
+			/** @type {Watcher<FileWatcherEvents>} */
+			(this.parentWatcher).on("change", (mtime, type) => {
+				if (this.closed) return;
+
+				// On non-osx platforms we don't need this watcher to detect
+				// directory removal, as an EPERM error indicates that
+				if ((!IS_OSX || this.polledWatching) && this.parentWatcher) {
+					this.parentWatcher.close();
+					this.parentWatcher = null;
+				}
+				// Try to create the watcher when parent directory is found
+				if (!this.watcher) {
+					this.createWatcher();
+					this.doScan(false);
+
+					// directory was created so we emit an event
+					this.forEachWatcher(this.path, (w) =>
+						w.emit("change", this.path, mtime, type, false),
+					);
+				}
+			});
+			/** @type {Watcher<FileWatcherEvents>} */
+			(this.parentWatcher).on("remove", () => {
+				this.onDirectoryRemoved("parent directory removed");
+			});
+		}
+	}
+
+	/**
+	 * @param {boolean} initial true when initial, otherwise false
+	 */
+	doScan(initial) {
+		if (this.scanning) {
+			if (this.scanAgain) {
+				if (!initial) this.scanAgainInitial = false;
+			} else {
+				this.scanAgain = true;
+				this.scanAgainInitial = initial;
+			}
+			return;
+		}
+		this.scanning = true;
+		if (this.timeout) {
+			clearTimeout(this.timeout);
+			this.timeout = undefined;
+		}
+		process.nextTick(() => {
+			if (this.closed) return;
+			fs.readdir(this.path, (err, items) => {
+				if (this.closed) return;
+				if (err) {
+					if (err.code === "ENOENT" || err.code === "EPERM") {
+						this.onDirectoryRemoved("scan readdir failed");
+					} else {
+						this.onScanError(err);
+					}
+					this.initialScan = false;
+					this.initialScanFinished = Date.now();
+					if (initial) {
+						for (const watchers of this.watchers.values()) {
+							for (const watcher of watchers) {
+								if (watcher.checkStartTime(this.initialScanFinished, false)) {
+									watcher.emit(
+										"initial-missing",
+										"scan (parent directory missing in initial scan)",
+									);
+								}
+							}
+						}
+					}
+					if (this.scanAgain) {
+						this.scanAgain = false;
+						this.doScan(this.scanAgainInitial);
+					} else {
+						this.scanning = false;
+					}
+					return;
+				}
+				const itemPaths = new Set(
+					items.map((item) => path.join(this.path, item.normalize("NFC"))),
+				);
+				for (const file of this.files.keys()) {
+					if (!itemPaths.has(file)) {
+						this.setMissing(file, initial, "scan (missing)");
+					}
+				}
+				for (const directory of this.directories.keys()) {
+					if (!itemPaths.has(directory)) {
+						this.setMissing(directory, initial, "scan (missing)");
+					}
+				}
+				if (this.scanAgain) {
+					// Early repeat of scan
+					this.scanAgain = false;
+					this.doScan(initial);
+					return;
+				}
+				const itemFinished = needCalls(itemPaths.size + 1, () => {
+					if (this.closed) return;
+					this.initialScan = false;
+					this.initialScanRemoved = null;
+					this.initialScanFinished = Date.now();
+					if (initial) {
+						const missingWatchers = new Map(this.watchers);
+						missingWatchers.delete(withoutCase(this.path));
+						for (const item of itemPaths) {
+							missingWatchers.delete(withoutCase(item));
+						}
+						for (const watchers of missingWatchers.values()) {
+							for (const watcher of watchers) {
+								if (watcher.checkStartTime(this.initialScanFinished, false)) {
+									watcher.emit(
+										"initial-missing",
+										"scan (missing in initial scan)",
+									);
+								}
+							}
+						}
+					}
+					if (this.scanAgain) {
+						this.scanAgain = false;
+						this.doScan(this.scanAgainInitial);
+					} else {
+						this.scanning = false;
+						this.onScanFinished();
+					}
+				});
+				for (const itemPath of itemPaths) {
+					fs.lstat(itemPath, (err2, stats) => {
+						if (this.closed) return;
+						if (err2) {
+							if (
+								err2.code === "ENOENT" ||
+								err2.code === "EPERM" ||
+								err2.code === "EACCES" ||
+								err2.code === "EBUSY" ||
+								// TODO https://github.com/libuv/libuv/pull/4566
+								(err2.code === "EINVAL" && IS_WIN)
+							) {
+								this.setMissing(itemPath, initial, `scan (${err2.code})`);
+							} else {
+								this.onScanError(err2);
+							}
+							itemFinished();
+							return;
+						}
+						if (stats.isFile() || stats.isSymbolicLink()) {
+							if (stats.mtime) {
+								ensureFsAccuracy(+stats.mtime);
+							}
+							this.setFileTime(
+								itemPath,
+								+stats.mtime || +stats.ctime || 1,
+								initial,
+								true,
+								"scan (file)",
+							);
+						} else if (
+							stats.isDirectory() &&
+							(!initial || !this.directories.has(itemPath))
+						) {
+							this.setDirectory(
+								itemPath,
+								+stats.birthtime || 1,
+								initial,
+								"scan (dir)",
+							);
+						}
+						itemFinished();
+					});
+				}
+				itemFinished();
+			});
+		});
+	}
+
+	/**
+	 * @returns {Record<string, number>} times
+	 */
+	getTimes() {
+		const obj = Object.create(null);
+		let safeTime = this.lastWatchEvent;
+		for (const [file, entry] of this.files) {
+			fixupEntryAccuracy(entry);
+			safeTime = Math.max(safeTime, entry.safeTime);
+			obj[file] = Math.max(entry.safeTime, entry.timestamp);
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				const times =
+					/** @type {Watcher<DirectoryWatcherEvents>} */
+					(w).directoryWatcher.getTimes();
+				for (const file of Object.keys(times)) {
+					const time = times[file];
+					safeTime = Math.max(safeTime, time);
+					obj[file] = time;
+				}
+			}
+			obj[this.path] = safeTime;
+		}
+		if (!this.initialScan) {
+			for (const watchers of this.watchers.values()) {
+				for (const watcher of watchers) {
+					const { path } = watcher;
+					if (!Object.prototype.hasOwnProperty.call(obj, path)) {
+						obj[path] = null;
+					}
+				}
+			}
+		}
+		return obj;
+	}
+
+	/**
+	 * @param {TimeInfoEntries} fileTimestamps file timestamps
+	 * @param {TimeInfoEntries} directoryTimestamps directory timestamps
+	 * @returns {number} safe time
+	 */
+	collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
+		let safeTime = this.lastWatchEvent;
+		for (const [file, entry] of this.files) {
+			fixupEntryAccuracy(entry);
+			safeTime = Math.max(safeTime, entry.safeTime);
+			fileTimestamps.set(file, entry);
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				safeTime = Math.max(
+					safeTime,
+					/** @type {Watcher<DirectoryWatcherEvents>} */
+					(w).directoryWatcher.collectTimeInfoEntries(
+						fileTimestamps,
+						directoryTimestamps,
+					),
+				);
+			}
+			fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+			directoryTimestamps.set(this.path, {
+				safeTime,
+			});
+		} else {
+			for (const dir of this.directories.keys()) {
+				// No additional info about this directory
+				// but maybe another DirectoryWatcher has info
+				fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
+				if (!directoryTimestamps.has(dir)) {
+					directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
+				}
+			}
+			fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+			directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+		}
+		if (!this.initialScan) {
+			for (const watchers of this.watchers.values()) {
+				for (const watcher of watchers) {
+					const { path } = watcher;
+					if (!fileTimestamps.has(path)) {
+						fileTimestamps.set(path, null);
+					}
+				}
+			}
+		}
+		return safeTime;
+	}
+
+	close() {
+		this.closed = true;
+		this.initialScan = false;
+		if (this.watcher) {
+			this.watcher.close();
+			this.watcher = null;
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				/** @type {Watcher<DirectoryWatcherEvents>} */
+				(w).close();
+			}
+			this.directories.clear();
+		}
+		if (this.parentWatcher) {
+			this.parentWatcher.close();
+			this.parentWatcher = null;
+		}
+		this.emit("closed");
+	}
+}
+
+module.exports = DirectoryWatcher;
+module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY;
+module.exports.Watcher = Watcher;
Index: frontend/node_modules/watchpack/lib/LinkResolver.js
===================================================================
--- frontend/node_modules/watchpack/lib/LinkResolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/LinkResolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,114 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const fs = require("fs");
+const path = require("path");
+
+// macOS, Linux, and Windows all rely on these errors
+const EXPECTED_ERRORS = new Set(["EINVAL", "ENOENT"]);
+
+// On Windows there is also this error in some cases
+if (process.platform === "win32") EXPECTED_ERRORS.add("UNKNOWN");
+
+class LinkResolver {
+	constructor() {
+		/** @type {Map<string, readonly string[]>} */
+		this.cache = new Map();
+	}
+
+	/**
+	 * @param {string} file path to file or directory
+	 * @returns {readonly string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file)
+	 */
+	resolve(file) {
+		const cacheEntry = this.cache.get(file);
+		if (cacheEntry !== undefined) {
+			return cacheEntry;
+		}
+		const parent = path.dirname(file);
+		if (parent === file) {
+			// At root of filesystem there can't be a link
+			const result = Object.freeze([file]);
+			this.cache.set(file, result);
+			return result;
+		}
+		// resolve the parent directory to find links there and get the real path
+		const parentResolved = this.resolve(parent);
+		let realFile = file;
+
+		// is the parent directory really somewhere else?
+		if (parentResolved[0] !== parent) {
+			// get the real location of file
+			const basename = path.basename(file);
+			realFile = path.resolve(parentResolved[0], basename);
+		}
+		// try to read the link content
+		try {
+			const linkContent = fs.readlinkSync(realFile);
+
+			// resolve the link content relative to the parent directory
+			const resolvedLink = path.resolve(parentResolved[0], linkContent);
+
+			// recursive resolve the link content for more links in the structure
+			const linkResolved = this.resolve(resolvedLink);
+
+			// merge parent and link resolve results
+			let result;
+			if (linkResolved.length > 1 && parentResolved.length > 1) {
+				// when both contain links we need to duplicate them with a Set
+				const resultSet = new Set(linkResolved);
+				// add the link
+				resultSet.add(realFile);
+				// add all symlinks of the parent
+				for (let i = 1; i < parentResolved.length; i++) {
+					resultSet.add(parentResolved[i]);
+				}
+				result = Object.freeze([...resultSet]);
+			} else if (parentResolved.length > 1) {
+				// we have links in the parent but not for the link content location
+				result = [...parentResolved];
+				// eslint-disable-next-line prefer-destructuring
+				result[0] = linkResolved[0];
+				// add the link
+				result.push(realFile);
+				Object.freeze(result);
+			} else if (linkResolved.length > 1) {
+				// we can return the link content location result
+				result = [...linkResolved];
+				// add the link
+				result.push(realFile);
+				Object.freeze(result);
+			} else {
+				// neither link content location nor parent have links
+				// this link is the only link here
+				result = Object.freeze([
+					// the resolve real location
+					linkResolved[0],
+					// add the link
+					realFile,
+				]);
+			}
+			this.cache.set(file, result);
+			return result;
+		} catch (err) {
+			if (
+				/** @type {NodeJS.ErrnoException} */
+				(err).code &&
+				!EXPECTED_ERRORS.has(/** @type {NodeJS.ErrnoException} */ (err).code)
+			) {
+				throw err;
+			}
+			// no link
+			const result = [...parentResolved];
+			result[0] = realFile;
+			Object.freeze(result);
+			this.cache.set(file, result);
+			return result;
+		}
+	}
+}
+
+module.exports = LinkResolver;
Index: frontend/node_modules/watchpack/lib/getWatcherManager.js
===================================================================
--- frontend/node_modules/watchpack/lib/getWatcherManager.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/getWatcherManager.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const path = require("path");
+const DirectoryWatcher = require("./DirectoryWatcher");
+
+/** @typedef {import("./index").EventMap} EventMap */
+/** @typedef {import("./DirectoryWatcher").DirectoryWatcherOptions} DirectoryWatcherOptions */
+/** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */
+/** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */
+/**
+ * @template {EventMap} T
+ * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher
+ */
+
+class WatcherManager {
+	/**
+	 * @param {DirectoryWatcherOptions=} options options
+	 */
+	constructor(options = {}) {
+		this.options = options;
+		/** @type {Map<string, DirectoryWatcher>} */
+		this.directoryWatchers = new Map();
+	}
+
+	/**
+	 * @param {string} directory a directory
+	 * @returns {DirectoryWatcher} a directory watcher
+	 */
+	getDirectoryWatcher(directory) {
+		const watcher = this.directoryWatchers.get(directory);
+		if (watcher === undefined) {
+			const newWatcher = new DirectoryWatcher(this, directory, this.options);
+			this.directoryWatchers.set(directory, newWatcher);
+			newWatcher.on("closed", () => {
+				this.directoryWatchers.delete(directory);
+			});
+			return newWatcher;
+		}
+		return watcher;
+	}
+
+	/**
+	 * @param {string} file file
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<FileWatcherEvents> | null} watcher or null if file has no directory
+	 */
+	watchFile(file, startTime) {
+		const directory = path.dirname(file);
+		if (directory === file) return null;
+		return this.getDirectoryWatcher(directory).watch(file, startTime);
+	}
+
+	/**
+	 * @param {string} directory directory
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<DirectoryWatcherEvents>} watcher
+	 */
+	watchDirectory(directory, startTime) {
+		return this.getDirectoryWatcher(directory).watch(directory, startTime);
+	}
+}
+
+const watcherManagers = new WeakMap();
+
+/**
+ * @param {DirectoryWatcherOptions} options options
+ * @returns {WatcherManager} the watcher manager
+ */
+module.exports = (options) => {
+	const watcherManager = watcherManagers.get(options);
+	if (watcherManager !== undefined) return watcherManager;
+	const newWatcherManager = new WatcherManager(options);
+	watcherManagers.set(options, newWatcherManager);
+	return newWatcherManager;
+};
+module.exports.WatcherManager = WatcherManager;
Index: frontend/node_modules/watchpack/lib/index.js
===================================================================
--- frontend/node_modules/watchpack/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,554 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const { EventEmitter } = require("events");
+const globToRegExp = require("glob-to-regexp");
+const LinkResolver = require("./LinkResolver");
+const getWatcherManager = require("./getWatcherManager");
+const watchEventSource = require("./watchEventSource");
+
+/** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
+/** @typedef {import("./DirectoryWatcher")} DirectoryWatcher */
+/** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */
+/** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */
+
+// eslint-disable-next-line jsdoc/reject-any-type
+/** @typedef {Record<string, (...args: any[]) => any>} EventMap */
+
+/**
+ * @template {EventMap} T
+ * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher
+ */
+
+/** @typedef {(item: string) => boolean} IgnoredFunction */
+/** @typedef {string[] | RegExp | string | IgnoredFunction} Ignored */
+
+/**
+ * @typedef {object} WatcherOptions
+ * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false
+ * @property {Ignored=} ignored ignore some files from watching (glob pattern or regexp)
+ * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false
+ */
+
+/** @typedef {WatcherOptions & { aggregateTimeout?: number }} WatchOptions */
+
+/**
+ * @typedef {object} NormalizedWatchOptions
+ * @property {boolean} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false
+ * @property {IgnoredFunction} ignored ignore some files from watching (glob pattern or regexp)
+ * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false
+ */
+
+/** @typedef {`scan (${string})` | "change" | "rename" | `watch ${string}` | `directory-removed ${string}`} EventType */
+/** @typedef {{ safeTime: number, timestamp: number, accuracy: number }} Entry */
+/** @typedef {{ safeTime: number }} OnlySafeTimeEntry */
+// eslint-disable-next-line jsdoc/ts-no-empty-object-type
+/** @typedef {{}} ExistenceOnlyTimeEntry */
+/** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null>} TimeInfoEntries */
+/** @typedef {Set<string>} Changes */
+/** @typedef {Set<string>} Removals */
+/** @typedef {{ changes: Changes, removals: Removals }} Aggregated */
+/** @typedef {{ files?: Iterable<string>, directories?: Iterable<string>, missing?: Iterable<string>, startTime?: number }} WatchMethodOptions */
+/** @typedef {Record<string, number>} Times */
+
+/**
+ * @param {MapIterator<WatchpackFileWatcher> | MapIterator<WatchpackDirectoryWatcher>} watchers watchers
+ * @param {Set<DirectoryWatcher>} set set
+ */
+function addWatchersToSet(watchers, set) {
+	for (const ww of watchers) {
+		const w = ww.watcher;
+		if (!set.has(w.directoryWatcher)) {
+			set.add(w.directoryWatcher);
+		}
+	}
+}
+
+/**
+ * @param {string} ignored ignored
+ * @returns {string | undefined} resolved global to regexp
+ */
+const stringToRegexp = (ignored) => {
+	if (ignored.length === 0) {
+		return;
+	}
+	const { source } = globToRegExp(ignored, { globstar: true, extended: true });
+	return `${source.slice(0, -1)}(?:$|\\/)`;
+};
+
+/**
+ * @param {Ignored=} ignored ignored
+ * @returns {(item: string) => boolean} ignored to function
+ */
+const ignoredToFunction = (ignored) => {
+	if (Array.isArray(ignored)) {
+		const stringRegexps = ignored.map((i) => stringToRegexp(i)).filter(Boolean);
+		if (stringRegexps.length === 0) {
+			return () => false;
+		}
+		const regexp = new RegExp(stringRegexps.join("|"));
+		return (item) => regexp.test(item.replace(/\\/g, "/"));
+	} else if (typeof ignored === "string") {
+		const stringRegexp = stringToRegexp(ignored);
+		if (!stringRegexp) {
+			return () => false;
+		}
+		const regexp = new RegExp(stringRegexp);
+		return (item) => regexp.test(item.replace(/\\/g, "/"));
+	} else if (ignored instanceof RegExp) {
+		return (item) => ignored.test(item.replace(/\\/g, "/"));
+	} else if (typeof ignored === "function") {
+		return ignored;
+	} else if (ignored) {
+		throw new Error(`Invalid option for 'ignored': ${ignored}`);
+	} else {
+		return () => false;
+	}
+};
+
+/**
+ * @param {WatchOptions} options options
+ * @returns {NormalizedWatchOptions} normalized options
+ */
+const normalizeOptions = (options) => ({
+	followSymlinks: Boolean(options.followSymlinks),
+	ignored: ignoredToFunction(options.ignored),
+	poll: options.poll,
+});
+
+const normalizeCache = new WeakMap();
+/**
+ * @param {WatchOptions} options options
+ * @returns {NormalizedWatchOptions} normalized options
+ */
+const cachedNormalizeOptions = (options) => {
+	const cacheEntry = normalizeCache.get(options);
+	if (cacheEntry !== undefined) return cacheEntry;
+	const normalized = normalizeOptions(options);
+	normalizeCache.set(options, normalized);
+	return normalized;
+};
+
+class WatchpackFileWatcher {
+	/**
+	 * @param {Watchpack} watchpack watchpack
+	 * @param {Watcher<FileWatcherEvents>} watcher watcher
+	 * @param {string | string[]} files files
+	 */
+	constructor(watchpack, watcher, files) {
+		/** @type {string[]} */
+		this.files = Array.isArray(files) ? files : [files];
+		this.watcher = watcher;
+		watcher.on("initial-missing", (type) => {
+			for (const file of this.files) {
+				if (!watchpack._missing.has(file)) {
+					watchpack._onRemove(file, file, type);
+				}
+			}
+		});
+		watcher.on("change", (mtime, type, _initial) => {
+			for (const file of this.files) {
+				watchpack._onChange(file, mtime, file, type);
+			}
+		});
+		watcher.on("remove", (type) => {
+			for (const file of this.files) {
+				watchpack._onRemove(file, file, type);
+			}
+		});
+	}
+
+	/**
+	 * @param {string | string[]} files files
+	 */
+	update(files) {
+		if (!Array.isArray(files)) {
+			if (this.files.length !== 1) {
+				this.files = [files];
+			} else if (this.files[0] !== files) {
+				this.files[0] = files;
+			}
+		} else {
+			this.files = files;
+		}
+	}
+
+	close() {
+		this.watcher.close();
+	}
+}
+
+class WatchpackDirectoryWatcher {
+	/**
+	 * @param {Watchpack} watchpack watchpack
+	 * @param {Watcher<DirectoryWatcherEvents>} watcher watcher
+	 * @param {string} directories directories
+	 */
+	constructor(watchpack, watcher, directories) {
+		/** @type {string[]} */
+		this.directories = Array.isArray(directories) ? directories : [directories];
+		this.watcher = watcher;
+		watcher.on("initial-missing", (type) => {
+			for (const item of this.directories) {
+				watchpack._onRemove(item, item, type);
+			}
+		});
+		watcher.on("change", (file, mtime, type, _initial) => {
+			for (const item of this.directories) {
+				watchpack._onChange(item, mtime, file, type);
+			}
+		});
+		watcher.on("remove", (type) => {
+			for (const item of this.directories) {
+				watchpack._onRemove(item, item, type);
+			}
+		});
+	}
+
+	/**
+	 * @param {string | string[]} directories directories
+	 */
+	update(directories) {
+		if (!Array.isArray(directories)) {
+			if (this.directories.length !== 1) {
+				this.directories = [directories];
+			} else if (this.directories[0] !== directories) {
+				this.directories[0] = directories;
+			}
+		} else {
+			this.directories = directories;
+		}
+	}
+
+	close() {
+		this.watcher.close();
+	}
+}
+
+/**
+ * @typedef {object} WatchpackEvents
+ * @property {(file: string, mtime: number, type: EventType) => void} change change event
+ * @property {(file: string, type: EventType) => void} remove remove event
+ * @property {(changes: Changes, removals: Removals) => void} aggregated aggregated event
+ */
+
+/**
+ * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>}
+ */
+class Watchpack extends EventEmitter {
+	/**
+	 * @param {WatchOptions=} options options
+	 */
+	constructor(options = {}) {
+		super();
+		if (!options) options = {};
+		/** @type {WatchOptions} */
+		this.options = options;
+		this.aggregateTimeout =
+			typeof options.aggregateTimeout === "number"
+				? options.aggregateTimeout
+				: 200;
+		/** @type {NormalizedWatchOptions} */
+		this.watcherOptions = cachedNormalizeOptions(options);
+		/** @type {WatcherManager} */
+		this.watcherManager = getWatcherManager(this.watcherOptions);
+		/** @type {Map<string, WatchpackFileWatcher>} */
+		this.fileWatchers = new Map();
+		/** @type {Map<string, WatchpackDirectoryWatcher>} */
+		this.directoryWatchers = new Map();
+		/** @type {Set<string>} */
+		this._missing = new Set();
+		this.startTime = undefined;
+		this.paused = false;
+		/** @type {Changes} */
+		this.aggregatedChanges = new Set();
+		/** @type {Removals} */
+		this.aggregatedRemovals = new Set();
+		/** @type {undefined | NodeJS.Timeout} */
+		this.aggregateTimer = undefined;
+		this._onTimeout = this._onTimeout.bind(this);
+	}
+
+	/**
+	 * @overload
+	 * @param {Iterable<string>} arg1 files
+	 * @param {Iterable<string>} arg2 directories
+	 * @param {number=} arg3 startTime
+	 * @returns {void}
+	 */
+	/**
+	 * @overload
+	 * @param {WatchMethodOptions} arg1 watch options
+	 * @returns {void}
+	 */
+	/**
+	 * @param {Iterable<string> | WatchMethodOptions} arg1 files
+	 * @param {Iterable<string>=} arg2 directories
+	 * @param {number=} arg3 startTime
+	 * @returns {void}
+	 */
+	watch(arg1, arg2, arg3) {
+		/** @type {Iterable<string> | undefined} */
+		let files;
+		/** @type {Iterable<string> | undefined} */
+		let directories;
+		/** @type {Iterable<string> | undefined} */
+		let missing;
+		/** @type {number | undefined} */
+		let startTime;
+		if (!arg2) {
+			({
+				files = [],
+				directories = [],
+				missing = [],
+				startTime,
+			} = /** @type {WatchMethodOptions} */ (arg1));
+		} else {
+			files = /** @type {Iterable<string>} */ (arg1);
+			directories = /** @type {Iterable<string>} */ (arg2);
+			missing = [];
+			startTime = /** @type {number} */ (arg3);
+		}
+		this.paused = false;
+		const { fileWatchers, directoryWatchers } = this;
+		const { ignored } = this.watcherOptions;
+		/**
+		 * @param {string} path path
+		 * @returns {boolean} true when need to filter, otherwise false
+		 */
+		const filter = (path) => !ignored(path);
+		/**
+		 * @template K, V
+		 * @param {Map<K, V | V[]>} map map
+		 * @param {K} key key
+		 * @param {V} item item
+		 */
+		const addToMap = (map, key, item) => {
+			const list = map.get(key);
+			if (list === undefined) {
+				map.set(key, item);
+			} else if (Array.isArray(list)) {
+				list.push(item);
+			} else {
+				map.set(key, [list, item]);
+			}
+		};
+		const fileWatchersNeeded = new Map();
+		const directoryWatchersNeeded = new Map();
+		/** @type {Set<string>} */
+		const missingFiles = new Set();
+		if (this.watcherOptions.followSymlinks) {
+			const resolver = new LinkResolver();
+			for (const file of files) {
+				if (filter(file)) {
+					for (const innerFile of resolver.resolve(file)) {
+						if (file === innerFile || filter(innerFile)) {
+							addToMap(fileWatchersNeeded, innerFile, file);
+						}
+					}
+				}
+			}
+			for (const file of missing) {
+				if (filter(file)) {
+					for (const innerFile of resolver.resolve(file)) {
+						if (file === innerFile || filter(innerFile)) {
+							missingFiles.add(file);
+							addToMap(fileWatchersNeeded, innerFile, file);
+						}
+					}
+				}
+			}
+			for (const dir of directories) {
+				if (filter(dir)) {
+					let first = true;
+					for (const innerItem of resolver.resolve(dir)) {
+						if (filter(innerItem)) {
+							addToMap(
+								first ? directoryWatchersNeeded : fileWatchersNeeded,
+								innerItem,
+								dir,
+							);
+						}
+						first = false;
+					}
+				}
+			}
+		} else {
+			for (const file of files) {
+				if (filter(file)) {
+					addToMap(fileWatchersNeeded, file, file);
+				}
+			}
+			for (const file of missing) {
+				if (filter(file)) {
+					missingFiles.add(file);
+					addToMap(fileWatchersNeeded, file, file);
+				}
+			}
+			for (const dir of directories) {
+				if (filter(dir)) {
+					addToMap(directoryWatchersNeeded, dir, dir);
+				}
+			}
+		}
+		// Close unneeded old watchers
+		// and update existing watchers
+		for (const [key, w] of fileWatchers) {
+			const needed = fileWatchersNeeded.get(key);
+			if (needed === undefined) {
+				w.close();
+				fileWatchers.delete(key);
+			} else {
+				w.update(needed);
+				fileWatchersNeeded.delete(key);
+			}
+		}
+		for (const [key, w] of directoryWatchers) {
+			const needed = directoryWatchersNeeded.get(key);
+			if (needed === undefined) {
+				w.close();
+				directoryWatchers.delete(key);
+			} else {
+				w.update(needed);
+				directoryWatchersNeeded.delete(key);
+			}
+		}
+		// Create new watchers and install handlers on these watchers
+		watchEventSource.batch(() => {
+			for (const [key, files] of fileWatchersNeeded) {
+				const watcher = this.watcherManager.watchFile(key, startTime);
+				if (watcher) {
+					fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files));
+				}
+			}
+			for (const [key, directories] of directoryWatchersNeeded) {
+				const watcher = this.watcherManager.watchDirectory(key, startTime);
+				if (watcher) {
+					directoryWatchers.set(
+						key,
+						new WatchpackDirectoryWatcher(this, watcher, directories),
+					);
+				}
+			}
+		});
+		this._missing = missingFiles;
+		this.startTime = startTime;
+	}
+
+	close() {
+		this.paused = true;
+		if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
+		for (const w of this.fileWatchers.values()) w.close();
+		for (const w of this.directoryWatchers.values()) w.close();
+		this.fileWatchers.clear();
+		this.directoryWatchers.clear();
+	}
+
+	pause() {
+		this.paused = true;
+		if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
+	}
+
+	/**
+	 * @returns {Record<string, number>} times
+	 */
+	getTimes() {
+		/** @type {Set<DirectoryWatcher>} */
+		const directoryWatchers = new Set();
+		addWatchersToSet(this.fileWatchers.values(), directoryWatchers);
+		addWatchersToSet(this.directoryWatchers.values(), directoryWatchers);
+		/** @type {Record<string, number>} */
+		const obj = Object.create(null);
+		for (const w of directoryWatchers) {
+			const times = w.getTimes();
+			for (const file of Object.keys(times)) obj[file] = times[file];
+		}
+		return obj;
+	}
+
+	/**
+	 * @returns {TimeInfoEntries} time info entries
+	 */
+	getTimeInfoEntries() {
+		/** @type {TimeInfoEntries} */
+		const map = new Map();
+		this.collectTimeInfoEntries(map, map);
+		return map;
+	}
+
+	/**
+	 * @param {TimeInfoEntries} fileTimestamps file timestamps
+	 * @param {TimeInfoEntries} directoryTimestamps directory timestamps
+	 */
+	collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
+		/** @type {Set<DirectoryWatcher>} */
+		const allWatchers = new Set();
+		addWatchersToSet(this.fileWatchers.values(), allWatchers);
+		addWatchersToSet(this.directoryWatchers.values(), allWatchers);
+		for (const w of allWatchers) {
+			w.collectTimeInfoEntries(fileTimestamps, directoryTimestamps);
+		}
+	}
+
+	/**
+	 * @returns {Aggregated} aggregated info
+	 */
+	getAggregated() {
+		if (this.aggregateTimer) {
+			clearTimeout(this.aggregateTimer);
+			this.aggregateTimer = undefined;
+		}
+		const changes = this.aggregatedChanges;
+		const removals = this.aggregatedRemovals;
+		this.aggregatedChanges = new Set();
+		this.aggregatedRemovals = new Set();
+		return { changes, removals };
+	}
+
+	/**
+	 * @param {string} item item
+	 * @param {number} mtime mtime
+	 * @param {string} file file
+	 * @param {EventType} type type
+	 */
+	_onChange(item, mtime, file, type) {
+		file = file || item;
+		if (!this.paused) {
+			this.emit("change", file, mtime, type);
+			if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
+			this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout);
+		}
+		this.aggregatedRemovals.delete(item);
+		this.aggregatedChanges.add(item);
+	}
+
+	/**
+	 * @param {string} item item
+	 * @param {string} file file
+	 * @param {EventType} type type
+	 */
+	_onRemove(item, file, type) {
+		file = file || item;
+		if (!this.paused) {
+			this.emit("remove", file, type);
+			if (this.aggregateTimer) clearTimeout(this.aggregateTimer);
+			this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout);
+		}
+		this.aggregatedChanges.delete(item);
+		this.aggregatedRemovals.add(item);
+	}
+
+	_onTimeout() {
+		this.aggregateTimer = undefined;
+		const changes = this.aggregatedChanges;
+		const removals = this.aggregatedRemovals;
+		this.aggregatedChanges = new Set();
+		this.aggregatedRemovals = new Set();
+		this.emit("aggregated", changes, removals);
+	}
+}
+
+module.exports = Watchpack;
Index: frontend/node_modules/watchpack/lib/reducePlan.js
===================================================================
--- frontend/node_modules/watchpack/lib/reducePlan.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/reducePlan.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,138 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const path = require("path");
+
+/**
+ * @template T
+ * @typedef {object} TreeNode
+ * @property {string} target target
+ * @property {TreeNode<T>} parent parent
+ * @property {TreeNode<T>[]} children children
+ * @property {number} entries number of entries
+ * @property {boolean} active true when active, otherwise false
+ * @property {T[] | T | undefined} value value
+ */
+
+/**
+ * @template T
+ * @param {Map<string, T[] | T>} plan plan
+ * @param {number} limit limit
+ * @returns {Map<string, Map<T, string>>} the new plan
+ */
+module.exports = (plan, limit) => {
+	const treeMap = new Map();
+	// Convert to tree
+	for (const [target, value] of plan) {
+		treeMap.set(target, {
+			target,
+			parent: undefined,
+			children: undefined,
+			entries: 1,
+			active: true,
+			value,
+		});
+	}
+	let currentCount = treeMap.size;
+	// Create parents and calculate sum of entries
+	for (const node of treeMap.values()) {
+		const parentPath = path.dirname(node.target);
+		if (parentPath !== node.target) {
+			let parent = treeMap.get(parentPath);
+			if (parent === undefined) {
+				parent = {
+					target: parentPath,
+					parent: undefined,
+					children: [node],
+					entries: node.entries,
+					active: false,
+					value: undefined,
+				};
+				treeMap.set(parentPath, parent);
+				node.parent = parent;
+			} else {
+				node.parent = parent;
+				if (parent.children === undefined) {
+					parent.children = [node];
+				} else {
+					parent.children.push(node);
+				}
+				do {
+					parent.entries += node.entries;
+					parent = parent.parent;
+				} while (parent);
+			}
+		}
+	}
+	// Reduce until limit reached
+	while (currentCount > limit) {
+		// Select node that helps reaching the limit most effectively without overmerging
+		const overLimit = currentCount - limit;
+		let bestNode;
+		let bestCost = Infinity;
+		for (const node of treeMap.values()) {
+			if (node.entries <= 1 || !node.children || !node.parent) continue;
+			if (node.children.length === 0) continue;
+			if (node.children.length === 1 && !node.value) continue;
+			// Try to select the node with has just a bit more entries than we need to reduce
+			// When just a bit more is over 30% over the limit,
+			// also consider just a bit less entries then we need to reduce
+			const cost =
+				node.entries - 1 >= overLimit
+					? node.entries - 1 - overLimit
+					: overLimit - node.entries + 1 + limit * 0.3;
+			if (cost < bestCost) {
+				bestNode = node;
+				bestCost = cost;
+			}
+		}
+		if (!bestNode) break;
+		// Merge all children
+		const reduction = bestNode.entries - 1;
+		bestNode.active = true;
+		bestNode.entries = 1;
+		currentCount -= reduction;
+		let { parent } = bestNode;
+		while (parent) {
+			parent.entries -= reduction;
+			parent = parent.parent;
+		}
+		const queue = new Set(bestNode.children);
+		for (const node of queue) {
+			node.active = false;
+			node.entries = 0;
+			if (node.children) {
+				for (const child of node.children) queue.add(child);
+			}
+		}
+	}
+	// Write down new plan
+	const newPlan = new Map();
+	for (const rootNode of treeMap.values()) {
+		if (!rootNode.active) continue;
+		const map = new Map();
+		const queue = new Set([rootNode]);
+		for (const node of queue) {
+			if (node.active && node !== rootNode) continue;
+			if (node.value) {
+				if (Array.isArray(node.value)) {
+					for (const item of node.value) {
+						map.set(item, node.target);
+					}
+				} else {
+					map.set(node.value, node.target);
+				}
+			}
+			if (node.children) {
+				for (const child of node.children) {
+					queue.add(child);
+				}
+			}
+		}
+		newPlan.set(rootNode.target, map);
+	}
+	return newPlan;
+};
Index: frontend/node_modules/watchpack/lib/watchEventSource.js
===================================================================
--- frontend/node_modules/watchpack/lib/watchEventSource.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/watchEventSource.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,449 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const { EventEmitter } = require("events");
+const fs = require("fs");
+const path = require("path");
+const reducePlan = require("./reducePlan");
+
+/** @typedef {import("fs").FSWatcher} FSWatcher */
+/** @typedef {import("./index").EventType} EventType */
+
+const IS_OSX = require("os").platform() === "darwin";
+const IS_WIN = require("os").platform() === "win32";
+
+const SUPPORTS_RECURSIVE_WATCHING = IS_OSX || IS_WIN;
+
+// Use 20 for OSX to make `FSWatcher.close` faster
+// https://github.com/nodejs/node/issues/29949
+const watcherLimit =
+	// @ts-expect-error avoid additional checks
+	+process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 20 : 10000);
+
+const recursiveWatcherLogging = Boolean(
+	process.env.WATCHPACK_RECURSIVE_WATCHER_LOGGING,
+);
+
+let isBatch = false;
+let watcherCount = 0;
+
+/** @type {Map<Watcher, string>} */
+const pendingWatchers = new Map();
+
+/** @type {Map<string, RecursiveWatcher>} */
+const recursiveWatchers = new Map();
+
+/** @type {Map<string, DirectWatcher>} */
+const directWatchers = new Map();
+
+/** @type {Map<Watcher, RecursiveWatcher | DirectWatcher>} */
+const underlyingWatcher = new Map();
+
+/**
+ * @param {string} filePath file path
+ * @returns {NodeJS.ErrnoException} new error with file path in the message
+ */
+function createEPERMError(filePath) {
+	const error =
+		/** @type {NodeJS.ErrnoException} */
+		(new Error(`Operation not permitted: ${filePath}`));
+	error.code = "EPERM";
+	return error;
+}
+
+/**
+ * @param {FSWatcher} watcher watcher
+ * @param {string} filePath a file path
+ * @param {(type: "rename" | "change", filename: string) => void} handleChangeEvent function to handle change
+ * @returns {(type: "rename" | "change", filename: string) => void} handler of change event
+ */
+function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {
+	return (type, filename) => {
+		// TODO: After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event.
+		// Here we just ignore it and keep the same behavior as before v22
+		// https://github.com/libuv/libuv/pull/4376
+		if (
+			type === "rename" &&
+			path.isAbsolute(filename) &&
+			path.basename(filename) === path.basename(filePath)
+		) {
+			if (!IS_OSX) {
+				// Before v22, windows will throw EPERM error
+				watcher.emit("error", createEPERMError(filename));
+			}
+			// Before v22, macos nothing to do
+			return;
+		}
+		handleChangeEvent(type, filename);
+	};
+}
+
+class DirectWatcher {
+	/**
+	 * @param {string} filePath file path
+	 */
+	constructor(filePath) {
+		this.filePath = filePath;
+		this.watchers = new Set();
+		/** @type {FSWatcher | undefined} */
+		this.watcher = undefined;
+		try {
+			const watcher = fs.watch(filePath);
+
+			this.watcher = watcher;
+			const handleChangeEvent = createHandleChangeEvent(
+				watcher,
+				filePath,
+				(type, filename) => {
+					for (const w of this.watchers) {
+						w.emit("change", type, filename);
+					}
+				},
+			);
+			watcher.on("change", handleChangeEvent);
+			watcher.on("error", (error) => {
+				for (const w of this.watchers) {
+					w.emit("error", error);
+				}
+			});
+		} catch (err) {
+			process.nextTick(() => {
+				for (const w of this.watchers) {
+					w.emit("error", err);
+				}
+			});
+		}
+		watcherCount++;
+	}
+
+	/**
+	 * @param {Watcher} watcher a watcher
+	 */
+	add(watcher) {
+		underlyingWatcher.set(watcher, this);
+		this.watchers.add(watcher);
+	}
+
+	/**
+	 * @param {Watcher} watcher a watcher
+	 */
+	remove(watcher) {
+		this.watchers.delete(watcher);
+		if (this.watchers.size === 0) {
+			directWatchers.delete(this.filePath);
+			watcherCount--;
+			if (this.watcher) this.watcher.close();
+		}
+	}
+
+	getWatchers() {
+		return this.watchers;
+	}
+}
+
+/** @typedef {Set<Watcher>} WatcherSet */
+
+class RecursiveWatcher {
+	/**
+	 * @param {string} rootPath a root path
+	 */
+	constructor(rootPath) {
+		this.rootPath = rootPath;
+		/** @type {Map<Watcher, string>} */
+		this.mapWatcherToPath = new Map();
+		/** @type {Map<string, WatcherSet>} */
+		this.mapPathToWatchers = new Map();
+		this.watcher = undefined;
+		try {
+			const watcher = fs.watch(rootPath, {
+				recursive: true,
+			});
+			this.watcher = watcher;
+			watcher.on("change", (type, filename) => {
+				if (!filename) {
+					if (recursiveWatcherLogging) {
+						process.stderr.write(
+							`[watchpack] dispatch ${type} event in recursive watcher (${this.rootPath}) to all watchers\n`,
+						);
+					}
+					for (const w of this.mapWatcherToPath.keys()) {
+						w.emit("change", /** @type {EventType} */ (type));
+					}
+				} else {
+					const dir = path.dirname(/** @type {string} */ (filename));
+					const watchers = this.mapPathToWatchers.get(dir);
+					if (recursiveWatcherLogging) {
+						process.stderr.write(
+							`[watchpack] dispatch ${type} event in recursive watcher (${
+								this.rootPath
+							}) for '${filename}' to ${
+								watchers ? watchers.size : 0
+							} watchers\n`,
+						);
+					}
+					if (watchers === undefined) return;
+					for (const w of watchers) {
+						w.emit(
+							"change",
+							/** @type {EventType} */ (type),
+							path.basename(/** @type {string} */ (filename)),
+						);
+					}
+				}
+			});
+			watcher.on("error", (error) => {
+				for (const w of this.mapWatcherToPath.keys()) {
+					w.emit("error", error);
+				}
+			});
+		} catch (err) {
+			process.nextTick(() => {
+				for (const w of this.mapWatcherToPath.keys()) {
+					w.emit("error", err);
+				}
+			});
+		}
+		watcherCount++;
+		if (recursiveWatcherLogging) {
+			process.stderr.write(
+				`[watchpack] created recursive watcher at ${rootPath}\n`,
+			);
+		}
+	}
+
+	/**
+	 * @param {string} filePath a file path
+	 * @param {Watcher} watcher a watcher
+	 */
+	add(filePath, watcher) {
+		underlyingWatcher.set(watcher, this);
+		const subpath = filePath.slice(this.rootPath.length + 1) || ".";
+		this.mapWatcherToPath.set(watcher, subpath);
+		const set = this.mapPathToWatchers.get(subpath);
+		if (set === undefined) {
+			const newSet = new Set();
+			newSet.add(watcher);
+			this.mapPathToWatchers.set(subpath, newSet);
+		} else {
+			set.add(watcher);
+		}
+	}
+
+	/**
+	 * @param {Watcher} watcher a watcher
+	 */
+	remove(watcher) {
+		const subpath = this.mapWatcherToPath.get(watcher);
+		if (!subpath) return;
+		this.mapWatcherToPath.delete(watcher);
+		const set = /** @type {WatcherSet} */ (this.mapPathToWatchers.get(subpath));
+		set.delete(watcher);
+		if (set.size === 0) {
+			this.mapPathToWatchers.delete(subpath);
+		}
+		if (this.mapWatcherToPath.size === 0) {
+			recursiveWatchers.delete(this.rootPath);
+			watcherCount--;
+			if (this.watcher) this.watcher.close();
+			if (recursiveWatcherLogging) {
+				process.stderr.write(
+					`[watchpack] closed recursive watcher at ${this.rootPath}\n`,
+				);
+			}
+		}
+	}
+
+	getWatchers() {
+		return this.mapWatcherToPath;
+	}
+}
+
+/**
+ * @typedef {object} WatcherEvents
+ * @property {(eventType: EventType, filename?: string) => void} change change event
+ * @property {(err: unknown) => void} error error event
+ */
+
+/**
+ * @extends {EventEmitter<{ [K in keyof WatcherEvents]: Parameters<WatcherEvents[K]> }>}
+ */
+class Watcher extends EventEmitter {
+	constructor() {
+		super();
+	}
+
+	close() {
+		if (pendingWatchers.has(this)) {
+			pendingWatchers.delete(this);
+			return;
+		}
+		const watcher = underlyingWatcher.get(this);
+		/** @type {RecursiveWatcher | DirectWatcher} */
+		(watcher).remove(this);
+		underlyingWatcher.delete(this);
+	}
+}
+
+/**
+ * @param {string} filePath a file path
+ * @returns {DirectWatcher} a directory watcher
+ */
+const createDirectWatcher = (filePath) => {
+	const existing = directWatchers.get(filePath);
+	if (existing !== undefined) return existing;
+	const w = new DirectWatcher(filePath);
+	directWatchers.set(filePath, w);
+	return w;
+};
+
+/**
+ * @param {string} rootPath a root path
+ * @returns {RecursiveWatcher} a recursive watcher
+ */
+const createRecursiveWatcher = (rootPath) => {
+	const existing = recursiveWatchers.get(rootPath);
+	if (existing !== undefined) return existing;
+	const w = new RecursiveWatcher(rootPath);
+	recursiveWatchers.set(rootPath, w);
+	return w;
+};
+
+const execute = () => {
+	/** @type {Map<string, Watcher[] | Watcher>} */
+	const map = new Map();
+	/**
+	 * @param {Watcher} watcher a watcher
+	 * @param {string} filePath a file path
+	 */
+	const addWatcher = (watcher, filePath) => {
+		const entry = map.get(filePath);
+		if (entry === undefined) {
+			map.set(filePath, watcher);
+		} else if (Array.isArray(entry)) {
+			entry.push(watcher);
+		} else {
+			map.set(filePath, [entry, watcher]);
+		}
+	};
+	for (const [watcher, filePath] of pendingWatchers) {
+		addWatcher(watcher, filePath);
+	}
+	pendingWatchers.clear();
+
+	// Fast case when we are not reaching the limit
+	if (!SUPPORTS_RECURSIVE_WATCHING || watcherLimit - watcherCount >= map.size) {
+		// Create watchers for all entries in the map
+		for (const [filePath, entry] of map) {
+			const w = createDirectWatcher(filePath);
+			if (Array.isArray(entry)) {
+				for (const item of entry) w.add(item);
+			} else {
+				w.add(entry);
+			}
+		}
+		return;
+	}
+
+	// Reconsider existing watchers to improving watch plan
+	for (const watcher of recursiveWatchers.values()) {
+		for (const [w, subpath] of watcher.getWatchers()) {
+			addWatcher(w, path.join(watcher.rootPath, subpath));
+		}
+	}
+	for (const watcher of directWatchers.values()) {
+		for (const w of watcher.getWatchers()) {
+			addWatcher(w, watcher.filePath);
+		}
+	}
+
+	// Merge map entries to keep watcher limit
+	// Create a 10% buffer to be able to enter fast case more often
+	const plan = reducePlan(map, watcherLimit * 0.9);
+
+	// Update watchers for all entries in the map
+	for (const [filePath, entry] of plan) {
+		if (entry.size === 1) {
+			for (const [watcher, filePath] of entry) {
+				const w = createDirectWatcher(filePath);
+				const old = underlyingWatcher.get(watcher);
+				if (old === w) continue;
+				w.add(watcher);
+				if (old !== undefined) old.remove(watcher);
+			}
+		} else {
+			const filePaths = new Set(entry.values());
+			if (filePaths.size > 1) {
+				const w = createRecursiveWatcher(filePath);
+				for (const [watcher, watcherPath] of entry) {
+					const old = underlyingWatcher.get(watcher);
+					if (old === w) continue;
+					w.add(watcherPath, watcher);
+					if (old !== undefined) old.remove(watcher);
+				}
+			} else {
+				for (const filePath of filePaths) {
+					const w = createDirectWatcher(filePath);
+					for (const watcher of entry.keys()) {
+						const old = underlyingWatcher.get(watcher);
+						if (old === w) continue;
+						w.add(watcher);
+						if (old !== undefined) old.remove(watcher);
+					}
+				}
+			}
+		}
+	}
+};
+
+module.exports.Watcher = Watcher;
+
+/**
+ * @param {() => void} fn a function
+ */
+module.exports.batch = (fn) => {
+	isBatch = true;
+	try {
+		fn();
+	} finally {
+		isBatch = false;
+		execute();
+	}
+};
+
+module.exports.createHandleChangeEvent = createHandleChangeEvent;
+
+module.exports.getNumberOfWatchers = () => watcherCount;
+
+/**
+ * @param {string} filePath a file path
+ * @returns {Watcher} watcher
+ */
+module.exports.watch = (filePath) => {
+	const watcher = new Watcher();
+	// Find an existing watcher
+	const directWatcher = directWatchers.get(filePath);
+	if (directWatcher !== undefined) {
+		directWatcher.add(watcher);
+		return watcher;
+	}
+	let current = filePath;
+	for (;;) {
+		const recursiveWatcher = recursiveWatchers.get(current);
+		if (recursiveWatcher !== undefined) {
+			recursiveWatcher.add(filePath, watcher);
+			return watcher;
+		}
+		const parent = path.dirname(current);
+		if (parent === current) break;
+		current = parent;
+	}
+	// Queue up watcher for creation
+	pendingWatchers.set(watcher, filePath);
+	if (!isBatch) execute();
+	return watcher;
+};
+
+module.exports.watcherLimit = watcherLimit;
Index: frontend/node_modules/watchpack/lib/watchpack.js
===================================================================
--- frontend/node_modules/watchpack/lib/watchpack.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/lib/watchpack.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Alexander Akait @akexander-akait
+*/
+"use strict";
+
+// TODO remove this file in the next major release
+module.exports = require("./index");
Index: frontend/node_modules/watchpack/package.json
===================================================================
--- frontend/node_modules/watchpack/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,74 @@
+{
+  "name": "watchpack",
+  "version": "2.5.1",
+  "description": "",
+  "homepage": "https://github.com/webpack/watchpack",
+  "bugs": {
+    "url": "https://github.com/webpack/watchpack/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/webpack/watchpack.git"
+  },
+  "license": "MIT",
+  "author": "Tobias Koppers @sokra",
+  "main": "lib/index.js",
+  "types": "types/index.js",
+  "directories": {
+    "test": "test"
+  },
+  "files": [
+    "lib/",
+    "types/"
+  ],
+  "scripts": {
+    "lint": "npm run lint:code && npm run lint:types && npm run lint:types-test && npm run lint:declarations && npm run fmt:check",
+    "lint:code": "eslint --cache .",
+    "lint:types": "tsc",
+    "lint:types-test": "tsc -p tsconfig.types.test.json",
+    "lint:declarations": "npm run fix:declarations && git diff --exit-code ./types",
+    "fix": "npm run fix:code && npm run fix:declarations",
+    "fix:code": "npm run lint:code -- --fix",
+    "fix:declarations": "tsc --noEmit false --declaration --emitDeclarationOnly --outDir types && npm run fmt -- ./types",
+    "fmt": "npm run fmt:base -- --log-level warn --write",
+    "fmt:check": "npm run fmt:base -- --check",
+    "fmt:base": "prettier --cache --ignore-unknown .",
+    "pretest": "npm run lint",
+    "test": "npm run test:coverage",
+    "test:base": "jest --runInBand",
+    "test:only": "npm run test:base",
+    "test:watch": "npm run test:base -- --watch",
+    "test:coverage": "npm run test:base -- --collectCoverageFrom=\"lib/**/*.js\" --coverage"
+  },
+  "dependencies": {
+    "glob-to-regexp": "^0.4.1",
+    "graceful-fs": "^4.1.2"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.28.0",
+    "@eslint/markdown": "^7.5.1",
+    "@stylistic/eslint-plugin": "^5.6.1",
+    "@types/glob-to-regexp": "^0.4.4",
+    "@types/graceful-fs": "^4.1.9",
+    "@types/jest": "^27.5.1",
+    "@types/node": "^24.10.4",
+    "eslint": "^9.39.2",
+    "eslint-config-prettier": "^10.1.8",
+    "eslint-config-webpack": "^4.7.3",
+    "eslint-plugin-import": "^2.32.0",
+    "eslint-plugin-jest": "^29.5.0",
+    "eslint-plugin-jsdoc": "^61.5.0",
+    "eslint-plugin-n": "^17.23.1",
+    "eslint-plugin-prettier": "^5.5.4",
+    "eslint-plugin-unicorn": "^62.0.0",
+    "globals": "^16.5.0",
+    "jest": "^27.5.1",
+    "prettier": "^3.7.4",
+    "rimraf": "^2.6.2",
+    "typescript": "^5.9.3",
+    "write-file-atomic": "^3.0.1"
+  },
+  "engines": {
+    "node": ">=10.13.0"
+  }
+}
Index: frontend/node_modules/watchpack/types/DirectoryWatcher.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/DirectoryWatcher.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/DirectoryWatcher.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,333 @@
+export = DirectoryWatcher;
+/** @typedef {Set<string>} InitialScanRemoved */
+/**
+ * @typedef {object} WatchpackEvents
+ * @property {(target: string, mtime: string, type: EventType, initial: boolean) => void} change change event
+ * @property {() => void} closed closed event
+ */
+/**
+ * @typedef {object} DirectoryWatcherOptions
+ * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false
+ * @property {IgnoredFunction=} ignored ignore some files from watching (glob pattern or regexp)
+ * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false
+ */
+/**
+ * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>}
+ */
+declare class DirectoryWatcher extends EventEmitter<{
+	/**
+	 * change event
+	 */
+	change: [
+		target: string,
+		mtime: string,
+		type: import("./index").EventType,
+		initial: boolean,
+	];
+	/**
+	 * closed event
+	 */
+	closed: [];
+}> {
+	/**
+	 * @param {WatcherManager} watcherManager a watcher manager
+	 * @param {string} directoryPath directory path
+	 * @param {DirectoryWatcherOptions=} options options
+	 */
+	constructor(
+		watcherManager: WatcherManager,
+		directoryPath: string,
+		options?: DirectoryWatcherOptions | undefined,
+	);
+	watcherManager: import("./getWatcherManager").WatcherManager;
+	options: DirectoryWatcherOptions;
+	path: string;
+	/** @type {Map<string, Entry>} */
+	files: Map<string, Entry>;
+	/** @type {Map<string, number>} */
+	filesWithoutCase: Map<string, number>;
+	/** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */
+	directories: Map<string, Watcher<DirectoryWatcherEvents> | boolean>;
+	lastWatchEvent: number;
+	initialScan: boolean;
+	ignored: import("./index").IgnoredFunction;
+	nestedWatching: boolean;
+	/** @type {number | false} */
+	polledWatching: number | false;
+	/** @type {undefined | NodeJS.Timeout} */
+	timeout: undefined | NodeJS.Timeout;
+	/** @type {null | InitialScanRemoved} */
+	initialScanRemoved: null | InitialScanRemoved;
+	/** @type {undefined | number} */
+	initialScanFinished: undefined | number;
+	/** @type {Map<string, Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>>} */
+	watchers: Map<
+		string,
+		Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>
+	>;
+	/** @type {Watcher<FileWatcherEvents> | null} */
+	parentWatcher: Watcher<FileWatcherEvents> | null;
+	refs: number;
+	/** @type {Map<string, boolean>} */
+	_activeEvents: Map<string, boolean>;
+	closed: boolean;
+	scanning: boolean;
+	scanAgain: boolean;
+	scanAgainInitial: boolean;
+	createWatcher(): void;
+	watcher: watchEventSource.Watcher | null | undefined;
+	/**
+	 * @template {(watcher: Watcher<EventMap>) => void} T
+	 * @param {string} path path
+	 * @param {T} fn function
+	 */
+	forEachWatcher<T extends (watcher: Watcher<EventMap>) => void>(
+		path: string,
+		fn: T,
+	): void;
+	/**
+	 * @param {string} itemPath an item path
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {EventType} type even type
+	 */
+	setMissing(itemPath: string, initial: boolean, type: EventType): void;
+	/**
+	 * @param {string} target a target to set file time
+	 * @param {number} mtime mtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {boolean} ignoreWhenEqual true to ignore when equal, otherwise false
+	 * @param {EventType} type type
+	 */
+	setFileTime(
+		target: string,
+		mtime: number,
+		initial: boolean,
+		ignoreWhenEqual: boolean,
+		type: EventType,
+	): void;
+	/**
+	 * @param {string} directoryPath directory path
+	 * @param {number} birthtime birthtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @param {EventType} type even type
+	 */
+	setDirectory(
+		directoryPath: string,
+		birthtime: number,
+		initial: boolean,
+		type: EventType,
+	): void;
+	/**
+	 * @param {string} directoryPath directory path
+	 */
+	createNestedWatcher(directoryPath: string): void;
+	/**
+	 * @param {boolean} flag true when nested, otherwise false
+	 */
+	setNestedWatching(flag: boolean): void;
+	/**
+	 * @param {string} target a target to watch
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} watcher
+	 */
+	watch(
+		target: string,
+		startTime?: number | undefined,
+	): Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>;
+	/**
+	 * @param {EventType} eventType event type
+	 * @param {string=} filename filename
+	 */
+	onWatchEvent(eventType: EventType, filename?: string | undefined): void;
+	/**
+	 * @param {unknown=} err error
+	 */
+	onWatcherError(err?: unknown | undefined): void;
+	/**
+	 * @param {Error | NodeJS.ErrnoException=} err error
+	 */
+	onStatsError(err?: (Error | NodeJS.ErrnoException) | undefined): void;
+	/**
+	 * @param {Error | NodeJS.ErrnoException=} err error
+	 */
+	onScanError(err?: (Error | NodeJS.ErrnoException) | undefined): void;
+	onScanFinished(): void;
+	/**
+	 * @param {string} reason a reason
+	 */
+	onDirectoryRemoved(reason: string): void;
+	watchInParentDirectory(): void;
+	/**
+	 * @param {boolean} initial true when initial, otherwise false
+	 */
+	doScan(initial: boolean): void;
+	/**
+	 * @returns {Record<string, number>} times
+	 */
+	getTimes(): Record<string, number>;
+	/**
+	 * @param {TimeInfoEntries} fileTimestamps file timestamps
+	 * @param {TimeInfoEntries} directoryTimestamps directory timestamps
+	 * @returns {number} safe time
+	 */
+	collectTimeInfoEntries(
+		fileTimestamps: TimeInfoEntries,
+		directoryTimestamps: TimeInfoEntries,
+	): number;
+	close(): void;
+}
+declare namespace DirectoryWatcher {
+	export {
+		EXISTANCE_ONLY_TIME_ENTRY,
+		Watcher,
+		IgnoredFunction,
+		EventType,
+		TimeInfoEntries,
+		Entry,
+		ExistenceOnlyTimeEntry,
+		OnlySafeTimeEntry,
+		EventMap,
+		WatcherManager,
+		EventSourceWatcher,
+		FileWatcherEvents,
+		DirectoryWatcherEvents,
+		InitialScanRemoved,
+		WatchpackEvents,
+		DirectoryWatcherOptions,
+	};
+}
+import { EventEmitter } from "events";
+/**
+ * @typedef {object} FileWatcherEvents
+ * @property {(type: EventType) => void} initial-missing initial missing event
+ * @property {(mtime: number, type: EventType, initial: boolean) => void} change change event
+ * @property {(type: EventType) => void} remove remove event
+ * @property {() => void} closed closed event
+ */
+/**
+ * @typedef {object} DirectoryWatcherEvents
+ * @property {(type: EventType) => void} initial-missing initial missing event
+ * @property {((file: string, mtime: number, type: EventType, initial: boolean) => void)} change change event
+ * @property {(type: EventType) => void} remove remove event
+ * @property {() => void} closed closed event
+ */
+/**
+ * @template {EventMap} T
+ * @extends {EventEmitter<{ [K in keyof T]: Parameters<T[K]> }>}
+ */
+declare class Watcher<T extends EventMap> extends EventEmitter<{
+	[K in keyof T]: Parameters<T[K]>;
+}> {
+	/**
+	 * @param {DirectoryWatcher} directoryWatcher a directory watcher
+	 * @param {string} target a target to watch
+	 * @param {number=} startTime start time
+	 */
+	constructor(
+		directoryWatcher: DirectoryWatcher,
+		target: string,
+		startTime?: number | undefined,
+	);
+	directoryWatcher: DirectoryWatcher;
+	path: string;
+	startTime: number | undefined;
+	/**
+	 * @param {number} mtime mtime
+	 * @param {boolean} initial true when initial, otherwise false
+	 * @returns {boolean} true of start time less than mtile, otherwise false
+	 */
+	checkStartTime(mtime: number, initial: boolean): boolean;
+	close(): void;
+}
+import watchEventSource = require("./watchEventSource");
+/** @typedef {import("./index").IgnoredFunction} IgnoredFunction */
+/** @typedef {import("./index").EventType} EventType */
+/** @typedef {import("./index").TimeInfoEntries} TimeInfoEntries */
+/** @typedef {import("./index").Entry} Entry */
+/** @typedef {import("./index").ExistenceOnlyTimeEntry} ExistenceOnlyTimeEntry */
+/** @typedef {import("./index").OnlySafeTimeEntry} OnlySafeTimeEntry */
+/** @typedef {import("./index").EventMap} EventMap */
+/** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
+/** @typedef {import("./watchEventSource").Watcher} EventSourceWatcher */
+/** @type {ExistenceOnlyTimeEntry} */
+declare const EXISTANCE_ONLY_TIME_ENTRY: ExistenceOnlyTimeEntry;
+type IgnoredFunction = import("./index").IgnoredFunction;
+type EventType = import("./index").EventType;
+type TimeInfoEntries = import("./index").TimeInfoEntries;
+type Entry = import("./index").Entry;
+type ExistenceOnlyTimeEntry = import("./index").ExistenceOnlyTimeEntry;
+type OnlySafeTimeEntry = import("./index").OnlySafeTimeEntry;
+type EventMap = import("./index").EventMap;
+type WatcherManager = import("./getWatcherManager").WatcherManager;
+type EventSourceWatcher = import("./watchEventSource").Watcher;
+type FileWatcherEvents = {
+	/**
+	 * initial missing event
+	 */
+	"initial-missing": (type: EventType) => void;
+	/**
+	 * change event
+	 */
+	change: (mtime: number, type: EventType, initial: boolean) => void;
+	/**
+	 * remove event
+	 */
+	remove: (type: EventType) => void;
+	/**
+	 * closed event
+	 */
+	closed: () => void;
+};
+type DirectoryWatcherEvents = {
+	/**
+	 * initial missing event
+	 */
+	"initial-missing": (type: EventType) => void;
+	/**
+	 * change event
+	 */
+	change: (
+		file: string,
+		mtime: number,
+		type: EventType,
+		initial: boolean,
+	) => void;
+	/**
+	 * remove event
+	 */
+	remove: (type: EventType) => void;
+	/**
+	 * closed event
+	 */
+	closed: () => void;
+};
+type InitialScanRemoved = Set<string>;
+type WatchpackEvents = {
+	/**
+	 * change event
+	 */
+	change: (
+		target: string,
+		mtime: string,
+		type: EventType,
+		initial: boolean,
+	) => void;
+	/**
+	 * closed event
+	 */
+	closed: () => void;
+};
+type DirectoryWatcherOptions = {
+	/**
+	 * true when need to resolve symlinks and watch symlink and real file, otherwise false
+	 */
+	followSymlinks?: boolean | undefined;
+	/**
+	 * ignore some files from watching (glob pattern or regexp)
+	 */
+	ignored?: IgnoredFunction | undefined;
+	/**
+	 * true when need to enable polling mode for watching, otherwise false
+	 */
+	poll?: (number | boolean) | undefined;
+};
Index: frontend/node_modules/watchpack/types/LinkResolver.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/LinkResolver.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/LinkResolver.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,10 @@
+export = LinkResolver;
+declare class LinkResolver {
+	/** @type {Map<string, readonly string[]>} */
+	cache: Map<string, readonly string[]>;
+	/**
+	 * @param {string} file path to file or directory
+	 * @returns {readonly string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file)
+	 */
+	resolve(file: string): readonly string[];
+}
Index: frontend/node_modules/watchpack/types/getWatcherManager.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/getWatcherManager.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/getWatcherManager.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+declare namespace _exports {
+	export {
+		EventMap,
+		DirectoryWatcherOptions,
+		DirectoryWatcherEvents,
+		FileWatcherEvents,
+		Watcher,
+	};
+}
+declare function _exports(options: DirectoryWatcherOptions): WatcherManager;
+declare namespace _exports {
+	export { WatcherManager };
+}
+export = _exports;
+type EventMap = import("./index").EventMap;
+type DirectoryWatcherOptions =
+	import("./DirectoryWatcher").DirectoryWatcherOptions;
+type DirectoryWatcherEvents =
+	import("./DirectoryWatcher").DirectoryWatcherEvents;
+type FileWatcherEvents = import("./DirectoryWatcher").FileWatcherEvents;
+type Watcher<T extends EventMap> = import("./DirectoryWatcher").Watcher<T>;
+/** @typedef {import("./index").EventMap} EventMap */
+/** @typedef {import("./DirectoryWatcher").DirectoryWatcherOptions} DirectoryWatcherOptions */
+/** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */
+/** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */
+/**
+ * @template {EventMap} T
+ * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher
+ */
+declare class WatcherManager {
+	/**
+	 * @param {DirectoryWatcherOptions=} options options
+	 */
+	constructor(options?: DirectoryWatcherOptions | undefined);
+	options: DirectoryWatcher.DirectoryWatcherOptions;
+	/** @type {Map<string, DirectoryWatcher>} */
+	directoryWatchers: Map<string, DirectoryWatcher>;
+	/**
+	 * @param {string} directory a directory
+	 * @returns {DirectoryWatcher} a directory watcher
+	 */
+	getDirectoryWatcher(directory: string): DirectoryWatcher;
+	/**
+	 * @param {string} file file
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<FileWatcherEvents> | null} watcher or null if file has no directory
+	 */
+	watchFile(
+		file: string,
+		startTime?: number | undefined,
+	): Watcher<FileWatcherEvents> | null;
+	/**
+	 * @param {string} directory directory
+	 * @param {number=} startTime start time
+	 * @returns {Watcher<DirectoryWatcherEvents>} watcher
+	 */
+	watchDirectory(
+		directory: string,
+		startTime?: number | undefined,
+	): Watcher<DirectoryWatcherEvents>;
+}
+import DirectoryWatcher = require("./DirectoryWatcher");
Index: frontend/node_modules/watchpack/types/index.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,261 @@
+export = Watchpack;
+/**
+ * @typedef {object} WatchpackEvents
+ * @property {(file: string, mtime: number, type: EventType) => void} change change event
+ * @property {(file: string, type: EventType) => void} remove remove event
+ * @property {(changes: Changes, removals: Removals) => void} aggregated aggregated event
+ */
+/**
+ * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>}
+ */
+declare class Watchpack extends EventEmitter<{
+	/**
+	 * change event
+	 */
+	change: [file: string, mtime: number, type: EventType];
+	/**
+	 * remove event
+	 */
+	remove: [file: string, type: EventType];
+	/**
+	 * aggregated event
+	 */
+	aggregated: [changes: Changes, removals: Removals];
+}> {
+	/**
+	 * @param {WatchOptions=} options options
+	 */
+	constructor(options?: WatchOptions | undefined);
+	/** @type {WatchOptions} */
+	options: WatchOptions;
+	aggregateTimeout: number;
+	/** @type {NormalizedWatchOptions} */
+	watcherOptions: NormalizedWatchOptions;
+	/** @type {WatcherManager} */
+	watcherManager: WatcherManager;
+	/** @type {Map<string, WatchpackFileWatcher>} */
+	fileWatchers: Map<string, WatchpackFileWatcher>;
+	/** @type {Map<string, WatchpackDirectoryWatcher>} */
+	directoryWatchers: Map<string, WatchpackDirectoryWatcher>;
+	/** @type {Set<string>} */
+	_missing: Set<string>;
+	startTime: number | undefined;
+	paused: boolean;
+	/** @type {Changes} */
+	aggregatedChanges: Changes;
+	/** @type {Removals} */
+	aggregatedRemovals: Removals;
+	/** @type {undefined | NodeJS.Timeout} */
+	aggregateTimer: undefined | NodeJS.Timeout;
+	_onTimeout(): void;
+	/**
+	 * @overload
+	 * @param {Iterable<string>} arg1 files
+	 * @param {Iterable<string>} arg2 directories
+	 * @param {number=} arg3 startTime
+	 * @returns {void}
+	 */
+	watch(
+		arg1: Iterable<string>,
+		arg2: Iterable<string>,
+		arg3?: number | undefined,
+	): void;
+	/**
+	 * @overload
+	 * @param {WatchMethodOptions} arg1 watch options
+	 * @returns {void}
+	 */
+	watch(arg1: WatchMethodOptions): void;
+	close(): void;
+	pause(): void;
+	/**
+	 * @returns {Record<string, number>} times
+	 */
+	getTimes(): Record<string, number>;
+	/**
+	 * @returns {TimeInfoEntries} time info entries
+	 */
+	getTimeInfoEntries(): TimeInfoEntries;
+	/**
+	 * @param {TimeInfoEntries} fileTimestamps file timestamps
+	 * @param {TimeInfoEntries} directoryTimestamps directory timestamps
+	 */
+	collectTimeInfoEntries(
+		fileTimestamps: TimeInfoEntries,
+		directoryTimestamps: TimeInfoEntries,
+	): void;
+	/**
+	 * @returns {Aggregated} aggregated info
+	 */
+	getAggregated(): Aggregated;
+	/**
+	 * @param {string} item item
+	 * @param {number} mtime mtime
+	 * @param {string} file file
+	 * @param {EventType} type type
+	 */
+	_onChange(item: string, mtime: number, file: string, type: EventType): void;
+	/**
+	 * @param {string} item item
+	 * @param {string} file file
+	 * @param {EventType} type type
+	 */
+	_onRemove(item: string, file: string, type: EventType): void;
+}
+declare namespace Watchpack {
+	export {
+		WatcherManager,
+		DirectoryWatcher,
+		DirectoryWatcherEvents,
+		FileWatcherEvents,
+		EventMap,
+		Watcher,
+		IgnoredFunction,
+		Ignored,
+		WatcherOptions,
+		WatchOptions,
+		NormalizedWatchOptions,
+		EventType,
+		Entry,
+		OnlySafeTimeEntry,
+		ExistenceOnlyTimeEntry,
+		TimeInfoEntries,
+		Changes,
+		Removals,
+		Aggregated,
+		WatchMethodOptions,
+		Times,
+		WatchpackEvents,
+	};
+}
+import { EventEmitter } from "events";
+declare class WatchpackFileWatcher {
+	/**
+	 * @param {Watchpack} watchpack watchpack
+	 * @param {Watcher<FileWatcherEvents>} watcher watcher
+	 * @param {string | string[]} files files
+	 */
+	constructor(
+		watchpack: Watchpack,
+		watcher: Watcher<FileWatcherEvents>,
+		files: string | string[],
+	);
+	/** @type {string[]} */
+	files: string[];
+	watcher: import("./DirectoryWatcher").Watcher<
+		import("./DirectoryWatcher").FileWatcherEvents
+	>;
+	/**
+	 * @param {string | string[]} files files
+	 */
+	update(files: string | string[]): void;
+	close(): void;
+}
+declare class WatchpackDirectoryWatcher {
+	/**
+	 * @param {Watchpack} watchpack watchpack
+	 * @param {Watcher<DirectoryWatcherEvents>} watcher watcher
+	 * @param {string} directories directories
+	 */
+	constructor(
+		watchpack: Watchpack,
+		watcher: Watcher<DirectoryWatcherEvents>,
+		directories: string,
+	);
+	/** @type {string[]} */
+	directories: string[];
+	watcher: import("./DirectoryWatcher").Watcher<
+		import("./DirectoryWatcher").DirectoryWatcherEvents
+	>;
+	/**
+	 * @param {string | string[]} directories directories
+	 */
+	update(directories: string | string[]): void;
+	close(): void;
+}
+type WatcherManager = import("./getWatcherManager").WatcherManager;
+type DirectoryWatcher = import("./DirectoryWatcher");
+type DirectoryWatcherEvents =
+	import("./DirectoryWatcher").DirectoryWatcherEvents;
+type FileWatcherEvents = import("./DirectoryWatcher").FileWatcherEvents;
+type EventMap = Record<string, (...args: any[]) => any>;
+type Watcher<T extends EventMap> = import("./DirectoryWatcher").Watcher<T>;
+type IgnoredFunction = (item: string) => boolean;
+type Ignored = string[] | RegExp | string | IgnoredFunction;
+type WatcherOptions = {
+	/**
+	 * true when need to resolve symlinks and watch symlink and real file, otherwise false
+	 */
+	followSymlinks?: boolean | undefined;
+	/**
+	 * ignore some files from watching (glob pattern or regexp)
+	 */
+	ignored?: Ignored | undefined;
+	/**
+	 * true when need to enable polling mode for watching, otherwise false
+	 */
+	poll?: (number | boolean) | undefined;
+};
+type WatchOptions = WatcherOptions & {
+	aggregateTimeout?: number;
+};
+type NormalizedWatchOptions = {
+	/**
+	 * true when need to resolve symlinks and watch symlink and real file, otherwise false
+	 */
+	followSymlinks: boolean;
+	/**
+	 * ignore some files from watching (glob pattern or regexp)
+	 */
+	ignored: IgnoredFunction;
+	/**
+	 * true when need to enable polling mode for watching, otherwise false
+	 */
+	poll?: (number | boolean) | undefined;
+};
+type EventType =
+	| `scan (${string})`
+	| "change"
+	| "rename"
+	| `watch ${string}`
+	| `directory-removed ${string}`;
+type Entry = {
+	safeTime: number;
+	timestamp: number;
+	accuracy: number;
+};
+type OnlySafeTimeEntry = {
+	safeTime: number;
+};
+type ExistenceOnlyTimeEntry = {};
+type TimeInfoEntries = Map<
+	string,
+	Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null
+>;
+type Changes = Set<string>;
+type Removals = Set<string>;
+type Aggregated = {
+	changes: Changes;
+	removals: Removals;
+};
+type WatchMethodOptions = {
+	files?: Iterable<string>;
+	directories?: Iterable<string>;
+	missing?: Iterable<string>;
+	startTime?: number;
+};
+type Times = Record<string, number>;
+type WatchpackEvents = {
+	/**
+	 * change event
+	 */
+	change: (file: string, mtime: number, type: EventType) => void;
+	/**
+	 * remove event
+	 */
+	remove: (file: string, type: EventType) => void;
+	/**
+	 * aggregated event
+	 */
+	aggregated: (changes: Changes, removals: Removals) => void;
+};
Index: frontend/node_modules/watchpack/types/reducePlan.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/reducePlan.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/reducePlan.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+declare namespace _exports {
+	export { TreeNode };
+}
+declare function _exports<T>(
+	plan: Map<string, T[] | T>,
+	limit: number,
+): Map<string, Map<T, string>>;
+export = _exports;
+type TreeNode<T> = {
+	/**
+	 * target
+	 */
+	target: string;
+	/**
+	 * parent
+	 */
+	parent: TreeNode<T>;
+	/**
+	 * children
+	 */
+	children: TreeNode<T>[];
+	/**
+	 * number of entries
+	 */
+	entries: number;
+	/**
+	 * true when active, otherwise false
+	 */
+	active: boolean;
+	/**
+	 * value
+	 */
+	value: T[] | T | undefined;
+};
Index: frontend/node_modules/watchpack/types/watchEventSource.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/watchEventSource.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/watchEventSource.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+export function batch(fn: () => void): void;
+export function getNumberOfWatchers(): number;
+export function watch(filePath: string): Watcher;
+export type FSWatcher = import("fs").FSWatcher;
+export type EventType = import("./index").EventType;
+export type WatcherSet = Set<Watcher>;
+export type WatcherEvents = {
+	/**
+	 * change event
+	 */
+	change: (eventType: EventType, filename?: string) => void;
+	/**
+	 * error event
+	 */
+	error: (err: unknown) => void;
+};
+/**
+ * @typedef {object} WatcherEvents
+ * @property {(eventType: EventType, filename?: string) => void} change change event
+ * @property {(err: unknown) => void} error error event
+ */
+/**
+ * @extends {EventEmitter<{ [K in keyof WatcherEvents]: Parameters<WatcherEvents[K]> }>}
+ */
+export class Watcher extends EventEmitter<{
+	/**
+	 * change event
+	 */
+	change: [
+		eventType: import("./index").EventType,
+		filename?: string | undefined,
+	];
+	/**
+	 * error event
+	 */
+	error: [err: unknown];
+}> {
+	constructor();
+	close(): void;
+}
+/**
+ * @param {FSWatcher} watcher watcher
+ * @param {string} filePath a file path
+ * @param {(type: "rename" | "change", filename: string) => void} handleChangeEvent function to handle change
+ * @returns {(type: "rename" | "change", filename: string) => void} handler of change event
+ */
+export function createHandleChangeEvent(
+	watcher: FSWatcher,
+	filePath: string,
+	handleChangeEvent: (type: "rename" | "change", filename: string) => void,
+): (type: "rename" | "change", filename: string) => void;
+export const watcherLimit: number;
+import { EventEmitter } from "events";
Index: frontend/node_modules/watchpack/types/watchpack.d.ts
===================================================================
--- frontend/node_modules/watchpack/types/watchpack.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/watchpack/types/watchpack.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+declare const _exports: typeof import("./index");
+export = _exports;
