diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b5008e777..fa10c0b64 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -5,10 +5,12 @@ on: branches: - main - next + - hot-middleware pull_request: branches: - main - next + - hot-middleware permissions: contents: read diff --git a/.gitignore b/.gitignore index 13bb32b4c..106a4173f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ logs npm-debug.log* .eslintcache .cspellcache +/client /dist /local /reports diff --git a/README.md b/README.md index 5b67deb5a..11eb8c9bc 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,77 @@ middleware(compiler, { }); ``` +## Hot Module Replacement client + +When the server is configured to serve the hot module replacement endpoint, the bundled application needs a small runtime that subscribes to that stream and applies the updates. `webpack-dev-middleware` ships that runtime under the `./client` subpath. Add it as a webpack entry next to your application code and enable `HotModuleReplacementPlugin`: + +```js +const webpack = require("webpack"); + +module.exports = { + entry: ["webpack-dev-middleware/client", "./src/app.js"], + plugins: [new webpack.HotModuleReplacementPlugin()], +}; +``` + +The runtime connects to `/__webpack_hmr` by default. Any of the options below can be set by adding a query string to the entry path: + +```js +entry: [ + "webpack-dev-middleware/client?reload=true&overlay=false", + "./src/app.js", +]; +``` + +### Client options + +| Name | Type | Default | Description | +| :-----------------: | :-------: | :--------------: | :------------------------------------------------------------------------------------------------------------------ | +| `path` | `string` | `/__webpack_hmr` | Path the SSE endpoint is served at. Must match the server `hot.path`. | +| `timeout` | `number` | `20000` | Reconnection / heartbeat watchdog timeout in milliseconds. | +| `overlay` | `boolean` | `true` | Show compile-time errors in an in-page overlay. | +| `overlayWarnings` | `boolean` | `false` | Also show compile-time warnings in the overlay. | +| `overlayStyles` | `Object` | `{}` | JSON object of CSS overrides for the overlay container. Pass JSON-encoded value via query string. | +| `ansiColors` | `Object` | `{}` | JSON object overriding the ANSI → HTML color map used by the overlay. | +| `reload` | `boolean` | `false` | Reload the page when an update cannot be applied through HMR. | +| `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | +| `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | +| `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. | +| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. | + +### Programmatic API + +`webpack-dev-middleware/client` also exports a few functions for advanced cases: + +```js +const hotClient = require("webpack-dev-middleware/client"); + +// Receive every HMR payload (building / built / sync / custom). +hotClient.subscribeAll((payload) => { + console.log("hot event", payload); +}); + +// Receive payloads whose `action` is not recognised by the client (i.e. custom +// payloads published via the server's `instance.context.hot.publish(...)`). +hotClient.subscribe((payload) => { + // do something +}); + +// Replace the default error overlay with your own implementation. +hotClient.useCustomOverlay({ + showProblems(type, lines) { + /* ... */ + }, + clear() { + /* ... */ + }, +}); + +// Connect manually when `autoConnect=false`. Accepts the same option keys as +// the query-string API above. +hotClient.setOptionsAndConnect({ path: "/__hmr" }); +``` + ## API `webpack-dev-middleware` also provides convenience methods that can be use to diff --git a/babel.config.js b/babel.config.js index 700d9fd7a..dc7d001f7 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,7 +1,4 @@ -const MIN_BABEL_VERSION = 7; - module.exports = (api) => { - api.assertVersion(MIN_BABEL_VERSION); api.cache(true); return { @@ -9,11 +6,28 @@ module.exports = (api) => { [ "@babel/preset-env", { + modules: false, targets: { - node: "20.9.0", + esmodules: true, + node: "0.12", }, }, ], ], + env: { + test: { + presets: [ + [ + "@babel/preset-env", + { + targets: { + node: "18.12.0", + }, + }, + ], + ], + plugins: ["@babel/plugin-transform-runtime"], + }, + }, }; }; diff --git a/client-src/globals.d.ts b/client-src/globals.d.ts new file mode 100644 index 000000000..7f66bdd08 --- /dev/null +++ b/client-src/globals.d.ts @@ -0,0 +1,28 @@ +/* eslint-disable */ + +declare module "ansi-html-community" { + function ansiHtmlCommunity(str: string): string; + namespace ansiHtmlCommunity { + function setColors(colors: Record): void; + } + export = ansiHtmlCommunity; +} + +interface ClientReporter { + cleanProblemsCache(): void; + problems( + type: "errors" | "warnings", + obj: { errors: string[]; warnings: string[]; name?: string }, + ): boolean; + success(): void; + useCustomOverlay(customOverlay: unknown): void; +} + +interface EventSourceWrapper { + addMessageListener(fn: (event: { data: string }) => void): void; +} + +interface Window { + __wdmEventSourceWrapper?: Record; + __webpack_dev_middleware_hot_reporter__?: ClientReporter; +} diff --git a/client-src/index.js b/client-src/index.js new file mode 100644 index 000000000..e66ccf651 --- /dev/null +++ b/client-src/index.js @@ -0,0 +1,361 @@ +/* global __resourceQuery, __webpack_public_path__ */ + +import stripAnsi from "strip-ansi"; + +import configureOverlay from "./overlay.js"; +import applyUpdate from "./process-update.js"; +import { log, setLogLevel } from "./utils/log.js"; + +/** @typedef {import("./utils/log.js").LogLevel} LogLevel */ + +/** + * @typedef {object} ClientOptions + * @property {string} path SSE endpoint path + * @property {number} timeout reconnection timeout in milliseconds + * @property {boolean} overlay enable the in-page error overlay + * @property {boolean} reload reload the page when HMR cannot apply the update + * @property {LogLevel} logging logger level + * @property {string} name limit updates to this compilation name + * @property {boolean} autoConnect connect immediately when the entry runs + * @property {Record} overlayStyles overrides for the overlay container CSS + * @property {boolean} overlayWarnings show warnings in the overlay too + * @property {Record} ansiColors overrides for ANSI → HTML color mapping + */ + +/** @type {ClientOptions} */ +const options = { + path: "/__webpack_hmr", + timeout: 20 * 1000, + overlay: true, + reload: false, + logging: "info", + name: "", + autoConnect: true, + overlayStyles: {}, + overlayWarnings: false, + ansiColors: {}, +}; + +setLogLevel(options.logging); + +/** + * @param {Record} overrides parsed query-string overrides + */ +function setOverrides(overrides) { + if (overrides.autoConnect) { + options.autoConnect = overrides.autoConnect === "true"; + } + if (overrides.path) options.path = overrides.path; + if (overrides.timeout) options.timeout = Number(overrides.timeout); + if (overrides.overlay) options.overlay = overrides.overlay !== "false"; + if (overrides.reload) options.reload = overrides.reload !== "false"; + if (overrides.logging) { + options.logging = /** @type {LogLevel} */ (overrides.logging); + } + if (overrides.name) { + options.name = overrides.name; + } + + if (overrides.dynamicPublicPath) { + options.path = __webpack_public_path__ + options.path; + } + + if (overrides.ansiColors) { + options.ansiColors = JSON.parse(overrides.ansiColors); + } + if (overrides.overlayStyles) { + options.overlayStyles = JSON.parse(overrides.overlayStyles); + } + + if (overrides.overlayWarnings) { + options.overlayWarnings = overrides.overlayWarnings === "true"; + } + + setLogLevel(options.logging); +} + +/** + * @typedef {(event: { data: string }) => void} MessageListener + */ + +/** + * @returns {{ addMessageListener: (fn: MessageListener) => void }} event source wrapper + */ +function createEventSourceWrapper() { + /** @type {EventSource} */ + let source; + let lastActivity = Date.now(); + /** @type {MessageListener[]} */ + const listeners = []; + /** @type {ReturnType} */ + let timer; + + const handleOnline = () => { + log.info("connected"); + lastActivity = Date.now(); + }; + + /** + * @param {{ data: string }} event event + */ + const handleMessage = (event) => { + lastActivity = Date.now(); + for (const listener of listeners) { + listener(event); + } + }; + + const handleDisconnect = () => { + clearInterval(timer); + source.close(); + setTimeout(init, /** @type {number} */ (options.timeout)); + }; + + /** + * Open the EventSource connection. + */ + function init() { + source = new window.EventSource(/** @type {string} */ (options.path)); + source.addEventListener("open", handleOnline); + source.addEventListener("error", handleDisconnect); + source.addEventListener("message", handleMessage); + } + + init(); + timer = setInterval( + () => { + if (Date.now() - lastActivity > /** @type {number} */ (options.timeout)) { + handleDisconnect(); + } + }, + /** @type {number} */ (options.timeout) / 2, + ); + + return { + addMessageListener(fn) { + listeners.push(fn); + }, + }; +} + +const WRAPPER_KEY = "__wdmEventSourceWrapper"; + +/** + * @returns {ReturnType} cached event source wrapper for this path + */ +function getEventSourceWrapper() { + const path = /** @type {string} */ (options.path); + if (!window[WRAPPER_KEY]) { + window[WRAPPER_KEY] = {}; + } + if (!window[WRAPPER_KEY][path]) { + // Cache the wrapper so multiple entries on the same page sharing the same + // `options.path` reuse a single SSE connection. + window[WRAPPER_KEY][path] = createEventSourceWrapper(); + } + return window[WRAPPER_KEY][path]; +} + +/** + * Subscribe the message handler to the shared event source wrapper. + */ +function connect() { + getEventSourceWrapper().addMessageListener((event) => { + if (event.data === "💓") { + return; + } + try { + processMessage(JSON.parse(event.data)); + } catch (err) { + log.warn(`Invalid HMR message: ${event.data}\n${err}`); + } + }); +} + +/** + * @param {Record} overrides overrides + */ +export function setOptionsAndConnect(overrides) { + setOverrides(overrides); + connect(); +} + +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ + +/** @typedef {{ name?: string, errors: string[], warnings: string[], hash: string, time?: number, modules?: Record, action?: string }} HMRPayload */ + +/** + * @returns {{ + * cleanProblemsCache: () => void, + * problems: (type: "errors" | "warnings", obj: HMRPayload) => boolean, + * success: () => void, + * useCustomOverlay: (customOverlay: EXPECTED_ANY) => void, + * }} reporter + */ +function createReporter() { + /** @type {EXPECTED_ANY} */ + let overlay; + if (typeof document !== "undefined" && options.overlay) { + overlay = configureOverlay({ + ansiColors: options.ansiColors, + overlayStyles: options.overlayStyles, + }); + } + + /** @type {string | null} */ + let previousProblems = null; + + /** + * @param {"errors" | "warnings"} type problem type + * @param {HMRPayload} obj payload + */ + const logProblems = (type, obj) => { + const newProblems = obj[type].map(stripAnsi).join("\n"); + if (previousProblems === newProblems) { + return; + } + previousProblems = newProblems; + + const name = obj.name ? `'${obj.name}' ` : ""; + const title = `bundle ${name}has ${obj[type].length} ${type}`; + if (type === "errors") { + log.error(title); + log.error(newProblems); + } else { + log.warn(title); + log.warn(newProblems); + } + }; + + return { + cleanProblemsCache() { + previousProblems = null; + }, + problems(type, obj) { + logProblems(type, obj); + if (overlay) { + if (options.overlayWarnings || type === "errors") { + overlay.showProblems(type, obj[type]); + return false; + } + overlay.clear(); + } + return true; + }, + success() { + if (overlay) overlay.clear(); + }, + useCustomOverlay(customOverlay) { + overlay = customOverlay; + }, + }; +} + +// The reporter is a singleton on the page so that, when multiple bundles +// include the client, errors are reported once but all clients receive them. +const REPORTER_KEY = "__webpack_dev_middleware_hot_reporter__"; +/** @type {ReturnType | undefined} */ +let reporter; + +/** @type {((obj: HMRPayload) => void) | undefined} */ +let customHandler; +/** @type {((obj: HMRPayload) => void) | undefined} */ +let subscribeAllHandler; + +/** + * @param {HMRPayload} obj payload + */ +function processMessage(obj) { + switch (obj.action) { + case "building": { + log.info(`bundle ${obj.name ? `'${obj.name}' ` : ""}rebuilding`); + break; + } + case "built": + case "sync": { + if (obj.action === "built") { + log.info( + `bundle ${obj.name ? `'${obj.name}' ` : ""}rebuilt in ${obj.time}ms`, + ); + } + if (obj.name && options.name && obj.name !== options.name) { + return; + } + let shouldApply = true; + if (obj.errors.length > 0) { + if (reporter) reporter.problems("errors", obj); + shouldApply = false; + } else if (obj.warnings.length > 0) { + if (reporter) { + shouldApply = reporter.problems("warnings", obj); + } + } else if (reporter) { + reporter.cleanProblemsCache(); + reporter.success(); + } + if (shouldApply) { + applyUpdate(obj.hash, obj.modules, options); + } + break; + } + default: { + if (customHandler) { + customHandler(obj); + } + } + } + + if (subscribeAllHandler) { + subscribeAllHandler(obj); + } +} + +// Bootstrap: parse query string overrides, then connect (if enabled). +if (typeof __resourceQuery === "string" && __resourceQuery.length > 0) { + const params = [...new URLSearchParams(__resourceQuery.slice(1))]; + /** @type {Record} */ + const overrides = {}; + for (const [key, value] of params) { + overrides[key] = value; + } + setOverrides(overrides); +} + +if (typeof window !== "undefined") { + if (!window[REPORTER_KEY]) { + window[REPORTER_KEY] = createReporter(); + } + reporter = window[REPORTER_KEY]; + + if (typeof window.EventSource === "undefined") { + log.warn( + "webpack-dev-middleware's hot client requires EventSource to work. " + + "Include a polyfill if you want to support this browser: " + + "https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools", + ); + } else if (options.autoConnect) { + connect(); + } +} + +/** + * @param {(obj: HMRPayload) => void} handler called for every incoming HMR message + */ +export function subscribeAll(handler) { + subscribeAllHandler = handler; +} + +/** + * @param {(obj: HMRPayload) => void} handler called for messages whose `action` is not recognized + */ +export function subscribe(handler) { + customHandler = handler; +} + +/** + * @param {EXPECTED_ANY} customOverlay replacement for the default error overlay + */ +export function useCustomOverlay(customOverlay) { + if (reporter) reporter.useCustomOverlay(customOverlay); +} diff --git a/client-src/overlay.js b/client-src/overlay.js new file mode 100644 index 000000000..a63596853 --- /dev/null +++ b/client-src/overlay.js @@ -0,0 +1,118 @@ +import ansiHTML from "ansi-html-community"; +import { encode as encodeHtmlEntity } from "html-entities"; + +const clientOverlay = document.createElement("div"); +clientOverlay.id = "webpack-dev-middleware-hot-overlay"; + +/** @type {Record} */ +const styles = { + background: "rgba(0,0,0,0.85)", + color: "#e8e8e8", + lineHeight: "1.6", + whiteSpace: "pre", + fontFamily: "Menlo, Consolas, monospace", + fontSize: "13px", + position: "fixed", + zIndex: 9999, + padding: "10px", + left: 0, + right: 0, + top: 0, + bottom: 0, + overflow: "auto", + dir: "ltr", + textAlign: "left", +}; + +/** @type {Record} */ +const colors = { + reset: ["transparent", "transparent"], + black: "181818", + red: "ff3348", + green: "3fff4f", + yellow: "ffd30e", + blue: "169be0", + magenta: "f840b7", + cyan: "0ad8e9", + lightgrey: "ebe7e3", + darkgrey: "6d7891", +}; + +/** + * @param {"errors" | "warnings"} type problem type + * @returns {string} HTML span with a colored badge + */ +function problemType(type) { + /** @type {Record} */ + const problemColors = { + errors: colors.red, + warnings: colors.yellow, + }; + const color = problemColors[type] || colors.red; + return ( + `' + + `${type.slice(0, -1).toUpperCase()}` + ); +} + +/** + * @param {"errors" | "warnings"} type problem type + * @param {string[]} lines messages to render + */ +export function showProblems(type, lines) { + clientOverlay.innerHTML = ""; + for (const line of lines) { + const msg = ansiHTML(encodeHtmlEntity(line)); + const div = document.createElement("div"); + div.style.marginBottom = "26px"; + div.innerHTML = `${problemType(type)} in ${msg}`; + clientOverlay.append(div); + } + if (document.body) { + document.body.append(clientOverlay); + } +} + +/** + * Remove the overlay container from the DOM. + */ +export function clear() { + if (clientOverlay.parentNode) { + clientOverlay.remove(); + } +} + +/** + * @param {{ ansiColors?: Record, overlayStyles?: Record }} options options + * @returns {{ showProblems: typeof showProblems, clear: typeof clear }} overlay api + */ +export default function configureOverlay(options) { + if (options.ansiColors) { + for (const color of Object.keys(options.ansiColors)) { + if (color in colors) { + colors[color] = options.ansiColors[color]; + } + } + ansiHTML.setColors(colors); + } + + if (options.overlayStyles) { + for (const style of Object.keys(options.overlayStyles)) { + styles[style] = options.overlayStyles[style]; + } + } + + for (const key of Object.keys(styles)) { + /** @type {EXPECTED_ANY} */ + (clientOverlay.style)[key] = styles[key]; + } + + return { + showProblems, + clear, + }; +} + +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ diff --git a/client-src/process-update.js b/client-src/process-update.js new file mode 100644 index 000000000..1d00d7c97 --- /dev/null +++ b/client-src/process-update.js @@ -0,0 +1,145 @@ +/* global __webpack_hash__ */ + +import { log } from "./utils/log.js"; + +const hot = import.meta.webpackHot; + +if (!hot) { + throw new Error("[HMR] Hot Module Replacement is disabled."); +} + +const HMR_DOCS_URL = "https://webpack.js.org/concepts/hot-module-replacement/"; + +/** @type {string | undefined} */ +let lastHash; +/** @type {Record} */ +const failureStatuses = { abort: 1, fail: 1 }; + +/** @type {webpack.ApplyOptions} */ +const applyOptions = { + ignoreUnaccepted: true, + ignoreDeclined: true, + ignoreErrored: true, + onUnaccepted(event) { + log.warn( + `Ignored an update to unaccepted module ${event.chain.join(" -> ")}`, + ); + }, + onDeclined(event) { + log.warn( + `Ignored an update to declined module ${event.chain.join(" -> ")}`, + ); + }, + onErrored(event) { + log.error(event.error); + log.warn( + `Ignored an error while updating module ${event.moduleId} (${event.type})`, + ); + }, +}; + +/** + * @param {string=} hash latest webpack compilation hash + * @returns {boolean} true when the current bundle matches the latest hash + */ +function upToDate(hash) { + if (hash) lastHash = hash; + return lastHash === __webpack_hash__; +} + +/** + * @param {string} hash latest hash from the SSE payload + * @param {Record | undefined} moduleMap module id → name map + * @param {{ reload?: boolean }} options client options + */ +export default function applyUpdate(hash, moduleMap, options) { + const { reload } = options; + + /** + * Trigger a full page reload when HMR cannot apply the update. + */ + function performReload() { + if (reload) { + log.warn("Reloading page"); + window.location.reload(); + } + } + + /** + * @param {Error} err error + */ + function handleError(err) { + if (hot.status() in failureStatuses) { + log.warn("Cannot check for update (Full reload needed)"); + log.warn(err.stack || err.message); + performReload(); + return; + } + log.warn(`Update check failed: ${err.stack || err.message}`); + } + + /** + * @param {(string | number)[]} updatedModules ids of modules that were attempted to update + * @param {(string | number)[] | null | undefined} renewedModules ids of modules that were successfully renewed + */ + function logUpdates(updatedModules, renewedModules) { + const unacceptedModules = updatedModules.filter( + (moduleId) => !renewedModules || !renewedModules.includes(moduleId), + ); + + if (unacceptedModules.length > 0) { + log.warn( + "The following modules couldn't be hot updated: " + + "(Full reload needed)\n" + + "This is usually because the modules which have changed " + + "(and their parents) do not know how to hot reload themselves. " + + `See ${HMR_DOCS_URL} for more details.`, + ); + for (const moduleId of unacceptedModules) { + log.warn(` - ${(moduleMap && moduleMap[moduleId]) || moduleId}`); + } + performReload(); + return; + } + + if (!renewedModules || renewedModules.length === 0) { + log.info("Nothing hot updated."); + } else { + log.info("Updated modules:"); + for (const moduleId of renewedModules) { + log.info(` - ${(moduleMap && moduleMap[moduleId]) || moduleId}`); + } + } + + if (upToDate()) { + log.info("App is up to date."); + } + } + + /** + * Ask webpack for the next chunk of HMR updates and apply them. + */ + function check() { + hot + .check(false) + .then((updatedModules) => { + if (!updatedModules) { + log.warn("Cannot find update (Full reload needed)"); + log.warn("(Probably because of restarting the server)"); + performReload(); + return undefined; + } + + return hot.apply(applyOptions).then((renewedModules) => { + if (!upToDate()) check(); + logUpdates(updatedModules, renewedModules); + }); + }) + .catch(handleError); + } + + if (!upToDate(hash) && hot.status() === "idle") { + log.info("Checking for updates on the server..."); + check(); + } +} diff --git a/client-src/utils/log.js b/client-src/utils/log.js new file mode 100644 index 000000000..56f1d82d4 --- /dev/null +++ b/client-src/utils/log.js @@ -0,0 +1,18 @@ +// @ts-expect-error -- no published types for this entry point +import logger from "webpack/lib/logging/runtime.js"; + +const LOGGER_NAME = "webpack-dev-middleware"; +const DEFAULT_LEVEL = "info"; + +/** @typedef {false | true | "none" | "error" | "warn" | "info" | "log" | "verbose"} LogLevel */ + +/** + * @param {LogLevel} level log level (or `false` for off, `true` for default) + */ +export function setLogLevel(level) { + logger.configureDefaultLogger({ level }); +} + +setLogLevel(DEFAULT_LEVEL); + +export const log = logger.getLogger(LOGGER_NAME); diff --git a/cspell.config.json b/cspell.config.json new file mode 100644 index 000000000..bf5c179ed --- /dev/null +++ b/cspell.config.json @@ -0,0 +1,13 @@ +{ + "ignorePaths": [ + "/client/**", + "/dist/**", + "/node_modules/**", + "/coverage/**", + "/test/outputs/**", + "/test/fixtures/**", + "CHANGELOG.md", + "cspell.config.json" + ], + "words": ["Consolas", "cspellcache", "darkgrey", "eslintcache", "esmodules"] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index c2e588884..9f658762a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,11 @@ -import { defineConfig } from "eslint/config"; +import { defineConfig, globalIgnores } from "eslint/config"; import configs from "eslint-config-webpack/configs.js"; export default defineConfig([ + globalIgnores(["client/**/*"]), { extends: [configs["recommended-dirty"]], + ignores: ["client-src/**/*"], }, { files: ["test/helpers/runner.js"], @@ -11,4 +13,13 @@ export default defineConfig([ "n/hashbang": "off", }, }, + { + files: ["client-src/**/*"], + extends: [configs["browser-outdated-recommended-module"]], + rules: { + // Function declarations are hoisted; allow referencing them ahead of + // their definition for readability. + "no-use-before-define": ["error", { functions: false }], + }, + }, ]); diff --git a/lint-staged.config.js b/lint-staged.config.js index 301084338..e99f27917 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,7 +1,7 @@ module.exports = { "*": [ "prettier --cache --write --ignore-unknown", - "cspell --cache --no-must-find-files", + "cspell --cache --no-must-find-files --config cspell.config.json", ], "*.js": ["eslint --cache --fix"], }; diff --git a/package-lock.json b/package-lock.json index f10967036..2d238881c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,19 @@ "version": "8.0.3", "license": "MIT", "dependencies": { + "ansi-html-community": "^0.0.8", + "html-entities": "^2.6.0", "memfs": "^4.56.10", "mime-types": "^3.0.2", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "schema-utils": "^4.3.3" + "schema-utils": "^4.3.3", + "strip-ansi": "^6.0.1" }, "devDependencies": { "@babel/cli": "^7.16.7", "@babel/core": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-env": "^7.16.7", "@changesets/cli": "^2.30.0", "@changesets/get-github-info": "^0.8.0", @@ -35,7 +39,7 @@ "deepmerge": "^4.2.2", "del-cli": "^7.0.0", "eslint": "^9.28.0", - "eslint-config-webpack": "^4.9.5", + "eslint-config-webpack": "^4.9.6", "execa": "^9.6.1", "express": "^5.1.0", "express-4": "npm:express@^4", @@ -45,6 +49,7 @@ "hono": "^4.12.12", "husky": "^9.1.3", "jest": "^30.1.3", + "jest-environment-jsdom": "^30.4.1", "koa": "^3.0.0", "lint-staged": "^17.0.2", "npm-run-all": "^4.1.5", @@ -70,6 +75,27 @@ } } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/cli": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz", @@ -131,6 +157,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1558,6 +1585,41 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", @@ -2342,6 +2404,7 @@ "integrity": "sha512-IQA++Idqb8fZzkCbHq3+T+9yG9WpeaBxomOrG2KcR/Pj0CgnovzuApYKL2cc35UWLePboKinMeqEPiweFpHVug==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=22.18.0" } @@ -2423,7 +2486,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.1.1.tgz", "integrity": "sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", @@ -2563,14 +2627,16 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.15.tgz", "integrity": "sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -2768,7 +2834,8 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -2838,6 +2905,123 @@ "node": ">=22.18.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -3943,6 +4127,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", + "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/@jest/expect": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", @@ -5119,6 +5331,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5211,6 +5424,18 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5261,6 +5486,7 @@ "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5317,6 +5543,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -5342,17 +5575,18 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", - "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/type-utils": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -5365,7 +5599,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.0", + "@typescript-eslint/parser": "^8.59.3", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -5381,16 +5615,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", - "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3" }, "engines": { @@ -5406,14 +5641,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", - "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.0", - "@typescript-eslint/types": "^8.58.0", + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", "debug": "^4.4.3" }, "engines": { @@ -5428,14 +5663,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", - "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0" + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5446,9 +5681,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", - "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", "dev": true, "license": "MIT", "engines": { @@ -5463,15 +5698,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", - "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -5488,9 +5723,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", - "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", "dev": true, "license": "MIT", "engines": { @@ -5502,16 +5737,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", - "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.0", - "@typescript-eslint/tsconfig-utils": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -5540,9 +5775,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -5569,9 +5804,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -5582,16 +5817,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", - "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0" + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5606,13 +5841,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", - "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/types": "8.59.3", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -6114,6 +6349,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6144,12 +6380,23 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6239,6 +6486,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -6831,6 +7090,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7775,6 +8035,71 @@ "node": ">=10" } }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -7854,6 +8179,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", @@ -8279,6 +8611,19 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", @@ -8539,6 +8884,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8610,9 +8956,9 @@ } }, "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -8628,6 +8974,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8639,9 +8986,9 @@ } }, "node_modules/eslint-config-webpack": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/eslint-config-webpack/-/eslint-config-webpack-4.9.5.tgz", - "integrity": "sha512-A4FtsxyBZfLF69zX0+18EolUMNSkua3vKAz9GV0XovQIbK/TqE8qenXziq+sMvzIWFwTJXhhtd5gFTUjccuZmQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/eslint-config-webpack/-/eslint-config-webpack-4.9.6.tgz", + "integrity": "sha512-4g1VqqOVgPrO/2bh17qNRKsQK26Aw1WF9mVTnvF+rNTDIUUTx+IaukXqXlumzwApQ1GfJlOsdLvT6WER1SPePg==", "dev": true, "license": "MIT", "dependencies": { @@ -8651,18 +8998,18 @@ "detect-indent": "^7.0.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jest": "^29.15.1", + "eslint-plugin-jest": "^29.15.2", "eslint-plugin-jsdoc": "^62.9.0", - "eslint-plugin-n": "^17.24.0", + "eslint-plugin-n": "^18.0.1", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-unicorn": "^64.0.0", - "globals": "^17.4.0", + "globals": "^17.6.0", "jsonc-eslint-parser": "^3.1.0", - "semver": "^7.7.4", + "semver": "^7.8.0", "sort-package-json": "^3.6.0", - "typescript-eslint": "^8.58.0" + "typescript-eslint": "^8.59.3" }, "engines": { "node": ">= 20.9.0" @@ -8691,9 +9038,9 @@ } }, "node_modules/eslint-config-webpack/node_modules/globals": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -8704,9 +9051,9 @@ } }, "node_modules/eslint-config-webpack/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -8833,9 +9180,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "29.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", - "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", + "version": "29.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.2.tgz", + "integrity": "sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8936,9 +9283,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "17.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", - "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.0.1.tgz", + "integrity": "sha512-q3ARhk+eZRc7myR0KHx+R3/GJeOHF+Ir6PK95Pu2tEX8Sl/4BIpmmVLva2kPrjC2gCmn6WHlHm+3yeo6Rxhycw==", "dev": true, "license": "MIT", "dependencies": { @@ -8949,17 +9296,26 @@ "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" + "semver": "^7.6.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "eslint": ">=8.23.0" + "eslint": ">=8.57.1", + "ts-declaration-location": "^1.0.6", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "ts-declaration-location": { + "optional": true + }, + "typescript": { + "optional": true + } } }, "node_modules/eslint-plugin-n/node_modules/globals": { @@ -8976,9 +9332,9 @@ } }, "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -9053,9 +9409,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { @@ -9069,7 +9425,7 @@ "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react/node_modules/resolve": { @@ -10529,9 +10885,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -10837,6 +11193,7 @@ "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -10848,11 +11205,23 @@ "dev": true, "license": "ISC" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "dev": true, "funding": [ { "type": "github", @@ -10944,6 +11313,34 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-id": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", @@ -11513,6 +11910,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -11894,6 +12298,7 @@ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.4.2", "@jest/types": "30.4.1", @@ -12266,6 +12671,29 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-environment-jsdom": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", + "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/environment-jsdom-abstract": "30.4.1", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jest-environment-node": { "version": "30.4.1", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", @@ -12802,6 +13230,84 @@ "node": ">=20.0.0" } }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -15023,6 +15529,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15401,6 +15914,19 @@ "dev": true, "license": "MIT" }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -15644,6 +16170,7 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16284,6 +16811,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -16424,6 +16958,19 @@ "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -16448,6 +16995,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17234,7 +17782,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -17271,7 +17818,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17388,6 +17934,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/synckit": { "version": "0.11.12", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", @@ -17628,6 +18181,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17635,6 +18189,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -17692,6 +18266,19 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -17728,42 +18315,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-declaration-location": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", - "dev": true, - "funding": [ - { - "type": "ko-fi", - "url": "https://ko-fi.com/rebeccastevens" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": ">=4.0.0" - } - }, - "node_modules/ts-declaration-location/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -17804,7 +18355,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsscmp": { "version": "1.0.6", @@ -17951,6 +18503,7 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17960,16 +18513,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", - "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.0", - "@typescript-eslint/parser": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0" + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -18307,6 +18860,19 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -18344,6 +18910,7 @@ "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -18420,6 +18987,43 @@ "node": ">=4.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -18658,6 +19262,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -18671,6 +19297,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -18785,11 +19428,12 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index b87a0ce09..ab9d21e10 100644 --- a/package.json +++ b/package.json @@ -16,25 +16,36 @@ }, "license": "MIT", "author": "Tobias Koppers @sokra", + "exports": { + ".": { + "types": "./types/index.d.ts", + "default": "./dist/index.js" + }, + "./client": "./client/index.js", + "./package.json": "./package.json" + }, "main": "dist/index.js", "types": "types/index.d.ts", "files": [ + "client", "dist", "types" ], "scripts": { "lint:prettier": "prettier --cache --list-different .", "lint:code": "eslint --cache .", - "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", + "lint:spelling": "cspell --cache --no-must-find-files --quiet --config cspell.config.json \"**/*.*\"", "lint:types": "tsc --pretty --noEmit", + "lint:types-client": "tsc -p tsconfig.client.json --pretty", "lint": "npm-run-all -l -p \"lint:**\"", "fix:js": "npm run lint:code -- --fix", "fix:prettier": "npm run lint:prettier -- --write", "fix": "npm-run-all -l fix:js fix:prettier", - "clean": "del-cli dist types", + "clean": "del-cli client dist types", "prebuild": "npm run clean", "build:types": "tsc && prettier \"types/**/*.ts\" --write", "build:code": "babel src -d dist --copy-files", + "build:client": "babel client-src -d client --copy-files", "build": "npm-run-all -p \"build:**\"", "test:only": "node --experimental-vm-modules ./node_modules/jest-cli/bin/jest", "test:watch": "npm run test:only -- --watch", @@ -46,15 +57,19 @@ "release": "npm run build && changeset publish" }, "dependencies": { + "ansi-html-community": "^0.0.8", + "html-entities": "^2.6.0", "memfs": "^4.56.10", "mime-types": "^3.0.2", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "schema-utils": "^4.3.3" + "schema-utils": "^4.3.3", + "strip-ansi": "^6.0.1" }, "devDependencies": { "@babel/cli": "^7.16.7", "@babel/core": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-env": "^7.16.7", "@changesets/cli": "^2.30.0", "@changesets/get-github-info": "^0.8.0", @@ -72,7 +87,7 @@ "deepmerge": "^4.2.2", "del-cli": "^7.0.0", "eslint": "^9.28.0", - "eslint-config-webpack": "^4.9.5", + "eslint-config-webpack": "^4.9.6", "execa": "^9.6.1", "express": "^5.1.0", "express-4": "npm:express@^4", @@ -82,6 +97,7 @@ "hono": "^4.12.12", "husky": "^9.1.3", "jest": "^30.1.3", + "jest-environment-jsdom": "^30.4.1", "koa": "^3.0.0", "lint-staged": "^17.0.2", "npm-run-all": "^4.1.5", diff --git a/test/__snapshots__/client.test.js.snap.webpack5 b/test/__snapshots__/client.test.js.snap.webpack5 new file mode 100644 index 000000000..e03872bc6 --- /dev/null +++ b/test/__snapshots__/client.test.js.snap.webpack5 @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`client with default options does not show overlay on warning builds by default 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle has 1 warnings", + ], + [ + "[webpack-dev-middleware] This isn't great, but it's not terrible", + ], +] +`; + +exports[`client with default options shows overlay on errored builds 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle has 2 errors", + ], + [ + "[webpack-dev-middleware] Something broke +Actually, 2 things broke", + ], +] +`; + +exports[`client with logging option emits info-level logs (including the [webpack-dev-middleware] prefix) by default 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle rebuilt in 100ms", + ], +] +`; + +exports[`client with logging option logging=error silences info and warn but keeps error 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle has 1 errors", + ], + [ + "[webpack-dev-middleware] boom", + ], +] +`; + +exports[`client with logging option logging=warn silences info but keeps warn 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle has 1 warnings", + ], + [ + "[webpack-dev-middleware] something", + ], +] +`; + +exports[`client with overlayWarnings: true shows overlay on errored builds 1`] = ` +[ + [ + "[webpack-dev-middleware] bundle has 2 errors", + ], + [ + "[webpack-dev-middleware] Something broke +Actually, 2 things broke", + ], +] +`; diff --git a/test/client.test.js b/test/client.test.js new file mode 100644 index 000000000..10e1bfe91 --- /dev/null +++ b/test/client.test.js @@ -0,0 +1,648 @@ +/** + * @jest-environment jsdom + */ + +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ + +/** @type {EXPECTED_ANY} */ +let processUpdate; +/** @type {{ showProblems: jest.Mock, clear: jest.Mock }} */ +let clientOverlay; + +jest.mock("../client-src/process-update", () => { + const fn = jest.fn(); + return fn; +}); + +jest.mock("../client-src/overlay", () => { + const overlay = { showProblems: jest.fn(), clear: jest.fn() }; + const factory = jest.fn(() => overlay); + factory.__getOverlay = () => overlay; + return factory; +}); + +/** + * @param {EXPECTED_ANY} obj message payload + * @returns {{ data: string }} fake SSE event + */ +function makeMessage(obj) { + return { data: typeof obj === "string" ? obj : JSON.stringify(obj) }; +} + +/** + * Stub `EventSource` so each test can drive `message`/`error`/`open` events. + * @returns {EXPECTED_ANY} fake constructor + last instance accessor + */ +function makeEventSourceStub() { + /** @type {EXPECTED_ANY[]} */ + const instances = []; + function EventSourceStub(url) { + this.url = url; + this.listeners = { open: [], error: [], message: [] }; + this.closed = false; + this.addEventListener = (type, fn) => { + if (this.listeners[type]) this.listeners[type].push(fn); + }; + this.dispatch = (type, event) => { + for (const fn of this.listeners[type] || []) fn(event); + }; + this.onmessage = (event) => this.dispatch("message", event); + // eslint-disable-next-line jest/prefer-spy-on + this.close = jest.fn(() => { + this.closed = true; + }); + instances.push(this); + } + EventSourceStub.instances = instances; + EventSourceStub.lastInstance = () => instances[instances.length - 1]; + return EventSourceStub; +} + +/** + * Reset module state so each test loads a fresh client. The per-page + * singletons on `window` are NOT cleared here — the outer `afterEach` handles + * that, so tests that re-require the client on the same "page" can observe + * the wrapper being reused. + * @param {string=} resourceQuery `__resourceQuery` value injected by webpack + * @returns {EXPECTED_ANY} client module + */ +function loadClient(resourceQuery = "") { + jest.resetModules(); + globalThis.__resourceQuery = resourceQuery; + processUpdate = require("../client-src/process-update"); + processUpdate.mockReset(); + + const overlayFactory = require("../client-src/overlay"); + + clientOverlay = overlayFactory.__getOverlay(); + clientOverlay.showProblems.mockReset(); + clientOverlay.clear.mockReset(); + + return require("../client-src"); +} + +describe("client", () => { + afterEach(() => { + delete globalThis.__resourceQuery; + delete globalThis.EventSource; + delete globalThis.__wdmEventSourceWrapper; + delete globalThis.__webpack_dev_middleware_hot_reporter__; + jest.useRealTimers(); + }); + + describe("with default options", () => { + let EventSourceStub; + let client; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "log").mockImplementation(() => {}); + jest.spyOn(console, "warn").mockImplementation(() => {}); + jest.spyOn(console, "error").mockImplementation(() => {}); + client = loadClient(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("connects to /__webpack_hmr", () => { + expect(EventSourceStub.instances).toHaveLength(1); + expect(EventSourceStub.instances[0].url).toBe("/__webpack_hmr"); + }); + + it("triggers webpack on successful builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(processUpdate).toHaveBeenCalledTimes(1); + }); + + it("triggers webpack on successful syncs", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "sync", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(processUpdate).toHaveBeenCalledTimes(1); + }); + + it("calls subscribeAll handler on default messages", () => { + const spy = jest.fn(); + client.subscribeAll(spy); + const message = { + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }; + EventSourceStub.lastInstance().onmessage(makeMessage(message)); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(message); + }); + + it("calls subscribeAll handler on custom messages", () => { + const spy = jest.fn(); + client.subscribeAll(spy); + EventSourceStub.lastInstance().onmessage( + makeMessage({ action: "thingy" }), + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith({ action: "thingy" }); + }); + + it("calls only the custom handler for custom messages", () => { + const spy = jest.fn(); + client.subscribe(spy); + EventSourceStub.lastInstance().onmessage( + makeMessage({ custom: "thingy" }), + ); + EventSourceStub.lastInstance().onmessage( + makeMessage({ action: "built" }), + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith({ custom: "thingy" }); + expect(processUpdate).not.toHaveBeenCalled(); + }); + + it("does not trigger webpack on errored builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke"], + warnings: [], + modules: [], + }), + ); + expect(processUpdate).not.toHaveBeenCalled(); + }); + + it("shows overlay on errored builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke", "Actually, 2 things broke"], + warnings: [], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.showProblems).toHaveBeenCalledWith("errors", [ + "Something broke", + "Actually, 2 things broke", + ]); + expect(console.error.mock.calls).toMatchSnapshot(); + }); + + it("hides overlay after errored build is fixed", () => { + const es = EventSourceStub.lastInstance(); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke", "Actually, 2 things broke"], + warnings: [], + modules: [], + }), + ); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef2", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.clear).toHaveBeenCalledTimes(1); + }); + + it("hides overlay after errored build becomes a warning", () => { + const es = EventSourceStub.lastInstance(); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke", "Actually, 2 things broke"], + warnings: [], + modules: [], + }), + ); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef2", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.clear).toHaveBeenCalledTimes(1); + }); + + it("triggers webpack on warning builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + expect(processUpdate).toHaveBeenCalledTimes(1); + }); + + it("does not show overlay on warning builds by default", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + expect(clientOverlay.showProblems).not.toHaveBeenCalled(); + // Warnings still surface through the logger even when the overlay stays hidden. + expect(console.warn.mock.calls).toMatchSnapshot(); + }); + + it("shows overlay after warning build becomes an error", () => { + const es = EventSourceStub.lastInstance(); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef2", + errors: ["Something broke", "Actually, 2 things broke"], + warnings: [], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + }); + }); + + describe("with overlayWarnings: true", () => { + let EventSourceStub; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "log").mockImplementation(() => {}); + jest.spyOn(console, "warn").mockImplementation(() => {}); + jest.spyOn(console, "error").mockImplementation(() => {}); + loadClient("?overlayWarnings=true"); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("shows overlay on errored builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke", "Actually, 2 things broke"], + warnings: [], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.showProblems).toHaveBeenCalledWith("errors", [ + "Something broke", + "Actually, 2 things broke", + ]); + expect(console.error.mock.calls).toMatchSnapshot(); + }); + + it("shows overlay on warning builds", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.showProblems).toHaveBeenCalledWith("warnings", [ + "This isn't great, but it's not terrible", + ]); + }); + + it("hides overlay after warning build is fixed", () => { + const es = EventSourceStub.lastInstance(); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef2", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(1); + expect(clientOverlay.clear).toHaveBeenCalledTimes(1); + }); + + it("updates overlay after errored build becomes a warning", () => { + const es = EventSourceStub.lastInstance(); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["Something broke"], + warnings: [], + modules: [], + }), + ); + es.onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef2", + errors: [], + warnings: ["This isn't great, but it's not terrible"], + modules: [], + }), + ); + expect(clientOverlay.showProblems).toHaveBeenCalledTimes(2); + expect(clientOverlay.showProblems).toHaveBeenNthCalledWith(1, "errors", [ + "Something broke", + ]); + expect(clientOverlay.showProblems).toHaveBeenNthCalledWith( + 2, + "warnings", + ["This isn't great, but it's not terrible"], + ); + }); + }); + + describe("with name option", () => { + let EventSourceStub; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "log").mockImplementation(() => {}); + jest.spyOn(console, "warn").mockImplementation(() => {}); + loadClient("?name=test"); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("does not trigger webpack when event name differs", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + name: "foo", + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(processUpdate).not.toHaveBeenCalled(); + }); + + it("does not trigger webpack on sync when event name differs", () => { + EventSourceStub.lastInstance().onmessage( + makeMessage({ + name: "bar", + action: "sync", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(processUpdate).not.toHaveBeenCalled(); + }); + }); + + describe("connection lifecycle", () => { + let EventSourceStub; + let client; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "log").mockImplementation(() => {}); + jest.spyOn(console, "warn").mockImplementation(() => {}); + client = loadClient(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("ignores heartbeat messages", () => { + const handler = jest.fn(); + client.subscribeAll(handler); + EventSourceStub.lastInstance().dispatch("message", { data: "💓" }); + expect(handler).not.toHaveBeenCalled(); + expect(processUpdate).not.toHaveBeenCalled(); + }); + + it("warns on invalid JSON", () => { + EventSourceStub.lastInstance().dispatch("message", { data: "not-json{" }); + expect( + console.warn.mock.calls.some(([msg]) => + /Invalid HMR message/.test(msg), + ), + ).toBe(true); + }); + + it("reuses the EventSource wrapper across reloads on the same path", () => { + // Re-loading the entry on the same page should reuse the cached SSE + // connection rather than opening a new one. + jest.resetModules(); + require("../client-src"); + expect(EventSourceStub.instances).toHaveLength(1); + }); + + it("closes and re-opens the connection on timeout", () => { + // The watchdog interval is created during the client's first load. Fake + // timers must be enabled before that load so jest can drive it. + jest.useFakeTimers({ doNotFake: ["nextTick"] }); + // Drop the wrapper opened by the outer beforeEach so we get a fresh + // EventSource scheduled under fake timers. + delete globalThis.__wdmEventSourceWrapper; + EventSourceStub.instances.length = 0; + loadClient(); + + const [first] = EventSourceStub.instances; + expect(first.closed).toBe(false); + // The watchdog ticks at `timeout/2` and disconnects when + // `Date.now() - lastActivity > timeout`. 30s is enough to cross that + // boundary regardless of which tick reports it first. + jest.advanceTimersByTime(30 * 1000); + expect(first.closed).toBe(true); + // Reconnect is scheduled after `options.timeout` (20s). + jest.advanceTimersByTime(20 * 1000); + expect(EventSourceStub.instances).toHaveLength(2); + }); + }); + + describe("with logging option", () => { + let EventSourceStub; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "warn").mockImplementation(() => {}); + jest.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("emits info-level logs (including the [webpack-dev-middleware] prefix) by default", () => { + loadClient(); + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: [], + modules: [], + }), + ); + expect(console.info.mock.calls).toMatchSnapshot(); + }); + + it("logging=none silences every level", () => { + loadClient("?logging=none"); + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["boom"], + warnings: [], + modules: [], + }), + ); + expect(console.info).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.error).not.toHaveBeenCalled(); + }); + + it("logging=warn silences info but keeps warn", () => { + loadClient("?logging=warn&overlayWarnings=true"); + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: [], + warnings: ["something"], + modules: [], + }), + ); + expect(console.info).not.toHaveBeenCalled(); + expect(console.warn.mock.calls).toMatchSnapshot(); + }); + + it("logging=error silences info and warn but keeps error", () => { + loadClient("?logging=error"); + EventSourceStub.lastInstance().onmessage( + makeMessage({ + action: "built", + time: 100, + hash: "1234567890abcdef", + errors: ["boom"], + warnings: [], + modules: [], + }), + ); + expect(console.info).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.error.mock.calls).toMatchSnapshot(); + }); + }); + + describe("with no EventSource", () => { + beforeEach(() => { + delete globalThis.EventSource; + jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("emits a warning and does not connect", () => { + loadClient(); + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn.mock.calls[0][0]).toMatch(/EventSource/); + }); + }); +}); diff --git a/tsconfig.client.json b/tsconfig.client.json new file mode 100644 index 000000000..479ffba6d --- /dev/null +++ b/tsconfig.client.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["es5", "dom", "webworker", "es2022.error"], + "module": "esnext", + "moduleResolution": "bundler", + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "types": ["node", "webpack/module"], + "skipDefaultLibCheck": true, + "esModuleInterop": true + }, + "include": ["./client-src/**/*"] +}